export const DATABASE: string = "deno";
export const TABLE = {
TODO: "todo",
};
Nada de especial aqui, basta definir o nome do banco de dados junto com um objeto para as tabelas e depois exportá-lo. (Nosso projeto terá um banco de dados chamado “deno” e, dentro desse banco de dados, teremos apenas uma tabela chamada “todo”)
Próximo dentro db pasta crie outro arquivo chamado client.ts e adicione o seguinte conteúdo
import { Client } from "https://deno.land/x/mysql/mod.ts";
// config
import { DATABASE, TABLE } from "./config.ts";
const client = await new Client();
client.connect({
hostname: "127.0.0.1",
username: "root",
password: "",
db: "",
});
export default client;
Algumas coisas que estão acontecendo aqui, estamos importando Client de mysql biblioteca. Client nos ajudará a conectar-se ao nosso banco de dados e executar operações no banco de dados.
client.connect({
hostname: "127.0.0.1",
username: "root",
password: "",
db: "",
});Client fornece um método chamado connect que leva em objeto onde podemos fornecer o hostname username password & db. Com essas informações, ele pode estabelecer uma conexão com nossa instância do MySQL.
Verifique se o seu
usernamenão tempasswordentrará em conflito com a conexão com a biblioteca de MySQL da deno. Se você não sabe como fazer isso, leia esta diretriz que escrevi[[[[ligação]
eu deixei
databasecampo em branco aqui porque quero selecioná-lo manualmente no meu script que escreverei um pouco.
Vamos adicionar um script que inicialize um banco de dados chamado “deno”, selecione-o e dentro desse banco de dados crie uma tabela chamada “todo”
Dentro db/client.ts arquivo, vamos fazer algumas novas adições.
import { Client } from "https://deno.land/x/mysql/mod.ts";
// config
import { DATABASE, TABLE } from "./config.ts";
const client = await new Client();
client.connect({
hostname: "127.0.0.1",
username: "root",
password: "",
db: "",
});
const run = async () => {
// create database (if not created before)
await client.execute(`CREATE DATABASE IF NOT EXISTS ${DATABASE}`);
// select db
await client.execute(`USE ${DATABASE}`);
// delete table if it exists before
await client.execute(`DROP TABLE IF EXISTS ${TABLE.TODO}`);
// create table
await client.execute(`
CREATE TABLE ${TABLE.TODO} (
id int(11) NOT NULL AUTO_INCREMENT,
todo varchar(100) NOT NULL,
isCompleted boolean NOT NULL default false,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`);
};
run();
export default client;
O que está acontecendo de novo é que estamos importando DATABASE & TABLE do nosso arquivo de configuração e, em seguida, usar esses valores em uma nova função chamada run()
Vamos quebrar isso run() , adicionei comentários no arquivo para ajudar você a entender o fluxo de trabalho.
const run = async () => {
// create database (if not created before)
await client.execute(`CREATE DATABASE IF NOT EXISTS ${DATABASE}`);
// select db
await client.execute(`USE ${DATABASE}`);
// delete table if it exists before
await client.execute(`DROP TABLE IF EXISTS ${TABLE.TODO}`);
// create table
await client.execute(`
CREATE TABLE ${TABLE.TODO} (
id int(11) NOT NULL AUTO_INCREMENT,
todo varchar(100) NOT NULL,
isCompleted boolean NOT NULL default false,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`);
};
run();- Crie um banco de dados chamado
deno, se já existir, não faça nada. - Em seguida, selecione o banco de dados a ser usado. Que é chamado
deno“ - Excluir tabela dentro
denochamadotodose já existe. - Em seguida, crie uma nova tabela dentro
denodb e chame-otodo& defina a estrutura que é a seguinte; ele terá um incremento automático exclusivoidque será um número inteiro, outro campo chamadotodoque será uma string e, finalmente, um campo chamadoisCompletedque é um booleano. Eu também definoidcomo minha chave primária.
A razão pela qual escrevi esse script foi por motivos pessoais, porque não quero ter informações extras na instância do MySQL. Toda vez que executo esse script, apenas reinicializamos tudo.
Você não precisa adicionar este script. Mas se você não o fizer, terá que criar manualmente um banco de dados e a tabela.
IMPORTANTE mencionar aqui que você também pode obter referências da biblioteca deno mysql, bem como criação de db e em criação de tabela.
Voltando à nossa agenda, acabamos de conseguir duas coisas das quatro mencionadas no topo, que é
- Crie uma conexão com o banco de dados MySQL
- Escreva um pequeno script que redefina o banco de dados toda vez que iniciarmos o servidor Deno
Isso já é 50% do tutorial. Infelizmente, não podemos ver muita coisa acontecendo agora. Vamos adicionar rapidamente algumas funcionalidades para vê-las funcionando
Executando operações CRUD em uma tabela e adicionando a funcionalidade aos nossos controladores de API
Precisamos atualizar nossa interface Todo primeiro, vá para interfaces/Todo.ts arquivo e atualizar o conteúdo do arquivo
export default interface Todo {
id?: number,
todo?: string,
isCompleted?: boolean,
}
O que isso ? faz é que torna a chave no objeto opcional. A razão pela qual eu fiz isso é. Em funções diferentes passarei um objeto com apenas id ou todo, isCompleted ou todos eles de uma só vez id, todo, isCompleted que você verá mais tarde.
Se você quiser saber mais sobre propriedades opcionais em texto datilografado, vá até lá docs aqui para aprender mais sobre.
Em seguida, crie uma nova pasta chamada modelos & dentro dessa pasta, crie um arquivo chamado todo.ts. Vamos adicionar o seguinte conteúdo ao arquivo.
import client from "../db/client.ts";
// config
import { TABLE } from "../db/config.ts";
// Interface
import Todo from "../interfaces/Todo.ts";
export default {
/**
* Takes in the id params & checks if the todo item exists
* in the database
* @param id
* @returns boolean to tell if an entry of todo exits in table
*/
doesExistById: async ({ id }: Todo) => {},
/**
* Will return all the entries in the todo column
* @returns array of todos
*/
getAll: async () => {},
/**
* Takes in the id params & returns the todo item found
* against it.
* @param id
* @returns object of todo item
*/
getById: async ({ id }: Todo) => {},
/**
* Adds a new todo item to todo table
* @param todo
* @param isCompleted
*/
add: async (
{ todo, isCompleted }: Todo,
) => {},
/**
* Updates the content of a single todo item
* @param id
* @param todo
* @param isCompleted
* @returns integer (count of effect rows)
*/
updateById: async ({ id, todo, isCompleted }: Todo) => {},
/**
* Deletes a todo by ID
* @param id
* @returns integer (count of effect rows)
*/
deleteById: async ({ id }: Todo) => {},
};
No momento, as funções estão vazias, mas tudo bem. Vamos preenchê-los um por um.
Em seguida, vá para controllers/todo.ts e adicione o seguinte.
// interfaces
import Todo from "../interfaces/Todo.ts";
// models
import TodoModel from "../models/todo.ts";
export default {
/**
* @description Get all todos
* @route GET /todos
*/
getAllTodos: async ({ response }: { response: any }) => {},
/**
* @description Add a new todo
* @route POST /todos
*/
createTodo: async (
{ request, response }: { request: any; response: any },
) => {},
/**
* @description Get todo by id
* @route GET todos/:id
*/
getTodoById: async (
{ params, response }: { params: { id: string }; response: any },
) => {},
/**
* @description Update todo by id
* @route PUT todos/:id
*/
updateTodoById: async (
{ params, request, response }: {
params: { id: string };
request: any;
response: any;
},
) => {},
/**
* @description Delete todo by id
* @route DELETE todos/:id
*/
deleteTodoById: async (
{ params, response }: { params: { id: string }; response: any },
) => {},
};
Aqui também temos funções vazias. Vamos começar a preenchê-los.
[Get] all API
Dentro models/todo.ts adicionar definição para função getAll
import client from "../db/client.ts";
// config
import { TABLE } from "../db/config.ts";
// Interface
import Todo from "../interfaces/Todo.ts";
export default {
/**
* Will return all the entries in the todo column
* @returns array of todos
*/
getAll: async () => {
return await client.query(`SELECT * FROM ${TABLE.TODO}`);
},
}o Client também expõe outro método além connect (usamos o método “connect” em db/client.ts arquivo) e isso é query. o client.query método, vamos executar consultas MySQL diretamente do nosso código Deno como está.
Em seguida, vá para controllers/todo.ts adicionar definição para getAllTodos
// interfaces
import Todo from "../interfaces/Todo.ts";
// models
import TodoModel from "../models/todo.ts";
export default {
/**
* @description Get all todos
* @route GET /todos
*/
getAllTodos: async ({ response }: { response: any }) => {
try {
const data = await TodoModel.getAll();
response.status = 200;
response.body = {
success: true,
data,
};
} catch (error) {
response.status = 400;
response.body = {
success: false,
message: `Error: ${error}`,
};
}
},
}Tudo o que estamos fazendo é importar TodoModel e usando o método chamado getAll que acabamos de definir agora e, como retorna como uma promessa, envolvemos-no em assíncrono / espera.
O método TodoModel.getAll() nos devolverá uma matriz à qual simplesmente retornamos response.body com status definido como 200.
Se a promessa falhar ou houver outro erro, basta acessar nosso bloco catch e retornar um status de 400 com success false e defina o message para o que obtemos do bloco de captura como está.
É isso que terminamos. Vamos ligar o nosso terminal. Verifique se sua instância do MySQL está em execução. No seu tipo de terminal
$ deno run --allow-net server.ts Seu terminal deve ser algo como isto
Meu console está me dizendo duas coisas aqui.
- Um servidor Deno API em execução na porta 8080
- Outro que minha instância do MySQL está executando
127.0.0.1qual élocalhost
Vamos testar nossa API. estou usando carteiro aqui. Você pode usar seu cliente de API favorito.
No momento, ele nos retorna dados vazios, mas uma vez que adicionamos dados em nosso todo mesa. Isso nos devolverá todos aqui.
Impressionante 1 API abaixo mais 4 para ir.
[Post] adicionar uma API de tarefas
No models/todo.ts adicione a seguinte definição para add() função
export default {
/**
* Adds a new todo item to todo table
* @param todo
* @param isCompleted
*/
add: async (
{ todo, isCompleted }: Todo,
) => {
return await client.query(
`INSERT INTO ${TABLE.TODO}(todo, isCompleted) values(?, ?)`,
[
todo,
isCompleted,
],
);
},
}A função add recebe o objeto como argumento, que possui 2 itens todo & isCompleted
assim add: async ({ todo, isCompleted }: Todo) => {} também pode ser escrito como ({todo, isCompleted}: {todo:string, isCompleted:boolean}) Mas como já temos uma interface definida em nosso interfaces/Todo.ts arquivo que é
export default interface Todo {
id?: number,
todo?: string,
isCompleted?: boolean,
}
Podemos simplesmente escrever isso como add: async ({ todo, isCompleted }: Todo) => {} que informa ao texto que esta função possui 2 argumentos todo que é uma string e isCompleted que é um booleano.
Se você quiser ler mais sobre interfaces. O texto datilografado possui um excelente documento. Que você pode encontrar aqui
Dentro de nossa função, temos o seguinte
return await client.query(
`INSERT INTO ${TABLE.TODO}(todo, isCompleted) values(?, ?)`,
[
todo,
isCompleted,
],
);Esta consulta pode ser dividida em 2 partes
INSERT INTO ${TABLE.TODO}(todo, isCompleted) values(?, ?)Os dois pontos de interrogação aqui denotam um uso de variáveis dentro desta consulta.- A outra parte
[todo, isCompleted]são as variáveis que irão no primeira parte da consulta e ser substituído por(?, ?) Table.Todoé apenas uma string que vem do arquivodb/config.tsOndeTable.Todoo valor é “todo“
O próximo dentro da nossa controllers/todo.ts vá para a definição de createTodo() função
export default {
/**
* @description Add a new todo
* @route POST /todos
*/
createTodo: async (
{ request, response }: { request: any; response: any },
) => {
const body = await request.body();
if (!request.hasBody) {
response.status = 400;
response.body = {
success: false,
message: "No data provided",
};
return;
}
try {
await TodoModel.add(
{ todo: body.value.todo, isCompleted: false },
);
response.body = {
success: true,
message: "The record was added successfully",
};
} catch (error) {
response.status = 400;
response.body = {
success: false,
message: `Error: ${error}`,
};
}
},
}Vamos dividir isso em 2 partes
Parte 1
const body = await request.body();
if (!request.hasBody) {
response.status = 400;
response.body = {
success: false,
message: "No data provided",
};
return;
}Tudo o que estamos fazendo aqui é verificar se o usuário está enviando dados no corpo; caso contrário, retornamos um status 400 e no retorno do corpo success: false & message:
Parte 2
try {
await TodoModel.add(
{ todo: body.value.todo, isCompleted: false },
);
response.body = {
success: true,
message: "The record was added successfully",
};
} catch (error) {
response.status = 400;
response.body = {
success: false,
message: `Error: ${error}`,
};
}Se não houver erro TodoModel.add() é chamada de função que acabamos de definir e simplesmente retornar um status de 200 & mensagem de confirmação ao usuário em resposta de retorno.
Caso contrário, basta lançar um erro semelhante ao que fizemos na API anterior.
É isso que terminamos. Vamos ligar o nosso terminal. Verifique se sua instância do MySQL está em execução. No seu tipo de terminal
$ deno run --allow-net server.ts Vamos para carteiro & execute a rota da API para este controlador.
Isso é ótimo, agora temos 2 APIs funcionando. Apenas mais 3 para ir.
Sinta-se livre para dar uma olhada em todo o código fonte deste tutorial aqui
Se você é proveniente do tutorial do capítulo 1 e deseja ver a diferença entre os capítulos 1 e 2, pode ver a diferença de confirmações aqui
[GET] API de todo por id
Na tua models/todo.ts arquivo adicionar definição dessas duas funções doesExistById() & getById()
export default {
/**
* Takes in the id params & checks if the todo item exists
* in the database
* @param id
* @returns boolean to tell if an entry of todo exits in table
*/
doesExistById: async ({ id }: Todo) => {
const [result] = await client.query(
`SELECT COUNT(*) count FROM ${TABLE.TODO} WHERE id = ? LIMIT 1`,
[id],
);
return result.count > 0;
},
/**
* Takes in the id params & returns the todo item found
* against it.
* @param id
* @returns object of todo item
*/
getById: async ({ id }: Todo) => {
return await client.query(
`SELECT * FROM ${TABLE.TODO} WHERE id = ?`,
[id],
);
},
}Vamos falar sobre cada função, uma por uma.
- doesExistById recebe um
id& retorna umbooleanindicando se um todo específico existe no banco de dados ou não.
Vamos quebrar essa função
const [result] = await client.query(
`SELECT COUNT(*) count FROM ${TABLE.TODO} WHERE id = ? LIMIT 1`,
[id],
);
return result.count > 0;Simplesmente verificamos a contagem aqui na tabela em relação a um ID de tarefa específico; se a contagem for maior que zero, simplesmente retornamos true de outra forma false
- getById retorna o item de tarefa em relação a um ID específico.
return await client.query(
`SELECT * FROM ${TABLE.TODO} WHERE id = ?`,
[id],
);Estamos simplesmente executando uma consulta MySQL aqui para obter um todo por id e retornando o resultado como está.
Em seguida, vá para o seu controllers/todo.ts arquivar e definir definição para getTodoById método de controlador.
export default {
/**
* @description Get todo by id
* @route GET todos/:id
*/
getTodoById: async (
{ params, response }: { params: { id: string }; response: any },
) => {
try {
const isAvailable = await TodoModel.doesExistById(
{ id: Number(params.id) },
);
if (!isAvailable) {
response.status = 404;
response.body = {
success: false,
message: "No todo found",
};
return;
}
const todo = await TodoModel.getById({ id: Number(params.id) });
response.status = 200;
response.body = {
success: true,
data: todo,
};
} catch (error) {
response.status = 400;
response.body = {
success: false,
message: `Error: ${error}`,
};
}
},
}Vamos dividir isso em duas partes menores
const isAvailable = await TodoModel.doesExistById(
{ id: Number(params.id) },
);
if (!isAvailable) {
response.status = 404;
response.body = {
success: false,
message: "No todo found",
};
return;
}Primeiro, verificamos se o todo existe no banco de dados novamente e não um ID usando este método
const isAvailable = await TodoModel.doesExistById(
{ id: Number(params.id) },
);Aqui precisamos converter params.id dentro de Number porque nossa interface Todo aceita apenas id como um número. Em seguida, apenas passamos params.id para doesExistById método. Este método retornará como um booleano.
Depois, basta verificar se o todo não está disponível, retornar um 404 com nossa resposta padrão, como nas APIs anteriores, conforme escrito abaixo.
if (!isAvailable) {
response.status = 404;
response.body = {
success: false,
message: "No todo found",
};
return;
}Então nós temos
try {
const todo: Todo = await TodoModel.getById({ id: Number(params.id) });
response.status = 200;
response.body = {
success: true,
data: todo,
};
} catch (error) {
response.status = 400;
response.body = {
success: false,
message: `Error: ${error}`,
};Isso é semelhante ao que estávamos fazendo nas APIs anteriores, aqui estamos simplesmente obtendo dados do banco de dados definindo-o como variável todo e, em seguida, retornando a resposta e, se houver um erro, retorne ao usuário uma mensagem de erro padrão no bloco de captura.
É isso que terminamos. Vamos ligar o nosso terminal. Verifique se sua instância do MySQL está em execução. No seu tipo de terminal
$ deno run --allow-net server.ts Vamos para carteiro & execute a rota da API para este controlador.
Como toda vez que reiniciamos nosso servidor, redefinimos nosso banco de dados. Se você não deseja esse comportamento, pode simplesmente comentar a função de execução no arquivo
db/client.ts -> run()
Vamos criar um modelo para esta API primeiro. Vá em nosso models/todo.ts arquivo e adicionar definição para a função updateById
**
* Updates the content of a single todo item
* @param id
* @param todo
* @param isCompleted
* @returns integer (count of effect rows)
*/
updateById: async ({ id, todo, isCompleted }: Todo) => {
const result = await client.query(
`UPDATE ${TABLE.TODO} SET todo=?, isCompleted=? WHERE id=?`,
[
todo,
isCompleted,
id,
],
);
// return count of rows updated
return result.affectedRows;
},o updateById leva em 3 params id, todo, isCompleted
Simplesmente executamos uma consulta MySQL dentro desta função
onst result = await client.query(
`UPDATE ${TABLE.TODO} SET todo=?, isCompleted=? WHERE id=?`,
[
todo,
isCompleted,
id,
],
);O que atualiza um único item de tarefa todo & isCompleted por um específico id
Em seguida, simplesmente retornamos uma contagem de linhas atualizadas por esta consulta. Fazendo
// return count of rows updated
return result.affectedRows;A contagem será 0 ou 1, mas não será superior a 1. Como temos IDs exclusivos em nosso banco de dados e, ao mesmo tempo, vários nem todos podem existir com o mesmo ID.
Em seguida, vá para o nosso controllers/todo.ts arquivo e adicionar definição para updateTodoById função que é a seguinte
updateTodoById: async (
{ params, request, response }: {
params: { id: string };
request: any;
response: any;
},
) => {
try {
const isAvailable = await TodoModel.doesExistById(
{ id: Number(params.id) },
);
if (!isAvailable) {
response.status = 404;
response.body = {
success: false,
message: "No todo found",
};
return;
}
// if todo found then update todo
const body = await request.body();
const updatedRows = await TodoModel.updateById({
id: Number(params.id),
...body.value,
});
response.status = 200;
response.body = {
success: true,
message: `Successfully updated ${updatedRows} row(s)`,
};
} catch (error) {
response.status = 400;
response.body = {
success: false,
message: `Error: ${error}`,
};
}
},Isso é quase o mesmo das nossas APIs anteriores que acabamos de escrever. A parte nova aqui é essa;
// if todo found then update todo
const body = await request.body();
const updatedRows = await TodoModel.updateById({
id: Number(params.id),
...body.value,
});Simplesmente obtemos o corpo que o usuário nos envia em JSON e passamos o corpo para o nosso TodoModel.updateById função.
Temos que converter
idto Number para estar em conformidade com a interface Todo
A consulta é executada e simplesmente retorna a contagem de linhas atualizadas, que simplesmente retornamos em nossa resposta e, se houver um erro, ela simplesmente vai para o bloco catch, onde retornamos nossa mensagem de resposta padrão.
Vamos executar isso e ver se funciona.
Vamos ligar o nosso terminal. Verifique se sua instância do MySQL está em execução. No seu tipo de terminal
$ deno run --allow-net server.ts Vamos para carteiro & execute a rota da API para este controlador.
[DELETE] API de todo por id
Na tua models/todo.ts arquivo criar uma função chamada deleteById & defina como
/**
* Deletes a todo by ID
* @param id
* @returns integer (count of effect rows)
*/
deleteById: async ({ id }: Todo) => {
const result = await client.query(
`DELETE FROM ${TABLE.TODO} WHERE id = ?`,
[id],
);
// return count of rows updated
return result.affectedRows;
},Aqui simplesmente passamos id como um parâmetro e, em seguida, use a consulta de exclusão do MySQL. Em seguida, retornamos a contagem de linhas atualizadas. Qual será 0 ou 1, porque o ID de cada tarefa é único.
Em seguida, vá na sua controllers/todo.ts arquivar e definir deleteByTodoId método
/**
* @description Delete todo by id
* @route DELETE todos/:id
*/
deleteTodoById: async (
{ params, response }: { params: { id: string }; response: any },
) => {
try {
const updatedRows = await TodoModel.deleteById({
id: Number(params.id),
});
response.status = 200;
response.body = {
success: true,
message: `Successfully updated ${updatedRows} row(s)`,
};
} catch (error) {
response.status = 400;
response.body = {
success: false,
message: `Error: ${error}`,
};
}
},Isso é bem direto, passamos o params.id para nosso TodoModel.deleteById método e, em seguida, retornamos a contagem de linhas atualizadas com esta consulta.
Se algo der errado, o erro é lançado no bloco catch, que retorna nossa resposta de erro padrão.
Vamos verificar isso.
Verifique se sua instância do MySQL está em execução. No seu tipo de terminal
$ deno run --allow-net server.ts Vamos para carteiro & execute a rota da API para este controlador.
Com isso, terminamos o nosso tutorial deno + oak + mysql.
Todo o código está disponível aqui https://github.com/adeelibr/deno-playground Se você encontrou um problema, entre em contato ou sinta-se à vontade para fazer uma solicitação de recebimento. Darei crédito a você no repositório.
Se você achou útil, compartilhe-o e, como sempre, estou disponível em twitter @adeelibr. Gostaria de ouvir seus pensamentos sobre isso.