- Published on
Operações CRUD com Knex e SQLite3 no Node.js
- Authors
- Name
- Vinícius Delmo
Objetivos
Desenvolver um projeto de uma loja de carros utilizando tecnologias modernas e eficientes. Para construir esta aplicação, estão sendo utilizadas as ferramentas Express, TypeScript e Knex. Além disso, o banco de dados escolhido para ser utilizado nesta aplicação é o SQLite3, onde criaremos a tabela de carros e a tabela de marcas dos carros. As duas tabelas serão relacionadas por meio de Foreign key.
O objetivo desta aplicação é permitir que o usuário possa cadastrar, visualizar, editar e excluir os carros disponíveis na loja.
Tecnologias/softwares Utilizadas no projeto
- Node
- TypeScript
- Express
- KNEX
- SQLITE
- TablePlus
- Postman
Módulo 1 - Começando
Setup Inicial do Projeto
Crie a pasta “carStore”
Rode o comando:
npm init --y
Mais Informações
O comando "npm init --y" é usado para criar um arquivo "package.json" em um diretório vazio do projeto com configurações padrão. O parâmetro "--y" significa "yes" e é usado para aceitar todas as configurações padrão sem que o usuário precise fornecer entrada manualmente. Isso é útil quando você deseja criar rapidamente um arquivo "package.json" com as configurações padrão e não precisa alterá-las imediatamente. Depois de executar o comando, o arquivo "package.json" será gerado automaticamente com as informações padrão, como nome do projeto, descrição, versão, autor, licença, etc.
"package.json" é um arquivo utilizado em projetos JavaScript/Node.js para definir informações sobre o projeto, suas dependências, scripts e outras configurações. Ele é geralmente colocado no diretório raiz do projeto e pode ser criado manualmente ou automaticamente com a ajuda do comando "npm init" ou "yarn init".
O arquivo "package.json" contém informações sobre o nome do projeto, a versão, a descrição, os autores, as licenças, as dependências, as versões das dependências, os scripts e outras informações relevantes para o projeto.
Ele também pode ser utilizado para configurar o projeto para uso em diferentes ambientes (desenvolvimento, teste, produção), especificar a ordem de execução de scripts, definir variáveis de ambiente, entre outras coisas.
Rode o comando:
npm i express
Mais Informações
- O Express.js é um framework web para Node.js utilizado para desenvolver aplicativos web e APIs RESTful. Ele fornece uma série de recursos para facilitar o desenvolvimento de aplicativos web, como roteamento de URLs, gerenciamento de sessões, middleware, manipulação de requisições e respostas HTTP, e muito mais.
- O Express.js é conhecido por sua simplicidade, flexibilidade e facilidade de uso. Ele é amplamente utilizado em projetos Node.js, desde pequenos projetos até grandes aplicações empresariais.
- O framework possui uma grande comunidade de desenvolvedores ativos, que criam e mantêm pacotes adicionais para adicionar recursos adicionais ao Express.js. Além disso, o Express.js é compatível com a maioria dos bancos de dados e tem integração com outras bibliotecas e ferramentas populares de desenvolvimento web.
- Em resumo, o Express.js é um dos principais frameworks web para o desenvolvimento de aplicativos web em Node.js, e é uma escolha popular para desenvolvedores que desejam criar aplicativos escaláveis e eficientes.
Rode o comando:
npm i --save-dev @types/express
Mais Informações
- O comando "npm i --save-dev @types/express" é utilizado para instalar o pacote "@types/express" como uma dependência de desenvolvimento ("devDependency") em um projeto Node.js que utiliza o framework Express.js.
- O pacote "@types/express" contém arquivos de declaração de tipo para o Express.js, que são utilizados pelos editores de código e pelas ferramentas de desenvolvimento para fornecer informações sobre as classes, métodos, propriedades e outros elementos disponíveis na biblioteca.
- Ao instalar esse pacote como uma dependência de desenvolvimento, você pode ter certeza de que seu código estará em conformidade com as definições de tipo do Express.js, ajudando a prevenir erros e aumentando a produtividade durante o desenvolvimento. Além disso, ele permite que você utilize recursos avançados do seu editor de código, como o autocomplete, que ajuda a acelerar o desenvolvimento.
- O parâmetro "--save-dev" adiciona o pacote instalado como uma dependência de desenvolvimento no arquivo "package.json" do projeto, o que significa que o pacote só será necessário durante o desenvolvimento e não será incluído na versão final do aplicativo quando ele for implantado em produção.
Rode o comando:
npm i --save-dev nodemon
Mais Informações
- O "nodemon" é uma ferramenta que ajuda a monitorar mudanças nos arquivos do projeto e reinicia automaticamente o servidor sempre que houver uma mudança, facilitando o desenvolvimento e a depuração.
- Com o "nodemon", você pode economizar tempo e aumentar a produtividade durante o desenvolvimento, pois não precisa reiniciar manualmente o servidor a cada vez que fizer uma mudança em seus arquivos. Isso torna o processo de desenvolvimento mais ágil e eficiente.
TypeScript
Rode o comando:
npx tsc --init
Mais Informações
- O comando "npx tsc --init" é utilizado para inicializar um arquivo de configuração do TypeScript chamado "tsconfig.json" em um diretório de projeto.
- O arquivo "tsconfig.json" é utilizado pelo compilador do TypeScript (tsc) para definir opções de compilação para projetos TypeScript. Ele permite configurar várias opções, como a versão do ECMAScript a ser usada, os diretórios de entrada e saída, as configurações de geração de código e outras opções relevantes para o processo de compilação.
- Ao executar o comando "npx tsc --init", o TypeScript irá criar um arquivo "tsconfig.json" no diretório atual com algumas configurações padrão. Você pode então editar este arquivo para definir as configurações específicas do seu projeto
Rode o comando:
npm i --save-dev typescript
Mais Informações
- O comando "npm i --save-dev typescript" é utilizado para instalar o pacote "typescript" como uma dependência de desenvolvimento ("devDependency") em um projeto Node.js. O "typescript" é uma linguagem de programação que estende o JavaScript com recursos de tipagem estática, o que ajuda a identificar erros de compilação antes mesmo de executar o código.
- Com o "typescript", você pode escrever código mais seguro e escalável, pois o compilador do TypeScript pode detectar e prevenir erros comuns antes mesmo de executar o código. Além disso, o TypeScript oferece recursos avançados, como suporte a classes, interfaces, enums, genéricos e outros recursos que tornam o desenvolvimento mais fácil e eficiente.
Rode o comando:
npm i ts-node
Mais Informações
- O comando "npm i ts-node" é utilizado para instalar o pacote "ts-node" em um projeto Node.js que utiliza o TypeScript. O "ts-node" é um interpretador TypeScript para Node.js que permite executar diretamente arquivos TypeScript no ambiente Node.js, sem a necessidade de compilar manualmente para JavaScript antes.
- Ao utilizar o "ts-node", você pode economizar tempo durante o desenvolvimento, pois não precisa compilar manualmente seus arquivos TypeScript a cada vez que alterá-los. Além disso, o "ts-node" também oferece outras funcionalidades, como a compilação de módulos sob demanda, suporte para importação de arquivos JSON e outras opções avançadas de configuração.
No package.json faça as seguintes alterações:
//Antes "main": "src/index.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" //Depois "main": "index.ts", "scripts": { "start": "nodemon src/index.ts"
Ao final dessas instalações seu projeto terá os seguintes arquivos:
A seguir crie a seguinte estrutura de diretórios e arquivos:
- Pasta “src” na raiz do projeto
- arquivo “index.ts” na pasta “src”
- Pastas “controllers”, “database” e “routes” na pasta “src”
- arquivo “carsController.ts” na pasta “controllers”
- arquivos “cars.ts” e “index.ts” na pasta “routes”
Ao final seu projeto terá os seguintes arquivos:
Testando aplicação/express
Vamos utilizar o método GET do express para testar nossa aplicação e rodar na porta 3000
Acesse src/index.ts
Importe o express e suas interfaces de tipagem "Request" e "Response”
import express, { Request, Response } from 'express'
Crie uma instância do objeto app do express.
const app = express()
Crie o método HTTP GET
app.get('/', (req: Request, res: Response) => { res.send('Olá, mundo!') })
Especifique em qual porta a aplicação rodará:
const port = 3000 app.listen(port, () => { console.log(`Servidor rodando na porta ${port}`) })
Rode “npm start”
>> npm start > carstore@1.0.0 start > nodemon src/index.ts [nodemon] 2.0.22 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: ts,json [nodemon] starting `ts-node src/index.ts` Servidor rodando na porta 3000
No navegador digite: http://localhost:3000/ e o resultado será:
'Olá, mundo!'
Isso mostra que na rota especificada a aplicação utilizando o framework express está executando com sucesso.
Knex
O Knex.js é um query builder para Node.js que permite interagir com bancos de dados SQL de uma forma mais simples e segura, evitando a escrita de código SQL "na mão" e garantindo a proteção contra ataques de injeção de SQL.
O Knex.js permite criar e gerenciar conexões com bancos de dados, definir esquemas de tabelas e executar operações CRUD (Create, Read, Update, Delete) através de métodos JavaScript encadeados em uma sintaxe que se assemelha à linguagem SQL. O Knex.js é compatível com vários bancos de dados SQL, incluindo PostgreSQL, MySQL, SQLite3, Oracle e SQL Server.
Instale o KNEX com o comando:
npm i knex
Instale o SQLITE com o comando:
npm i sqlite3
Mais Informações
- O comando "npm i sqlite3" instala o pacote do Node.js que permite a conexão com bancos de dados SQLite3. O SQLite3 é um banco de dados relacional que é armazenado em um arquivo local, ao invés de ser executado em um servidor como outros bancos de dados, como o MySQL ou o PostgreSQL.
- Crie o arquivo “knexfile.ts” na raiz do projeto.
Mais Informações
- O arquivo knexfile.ts é utilizado pelo Knex.js para configurar as conexões com bancos de dados e gerenciar diferentes ambientes, como desenvolvimento, produção, teste, entre outros.
No arquivo criado insira o seguinte código de configuração do KNEX:
import type { Knex } from 'knex' const config: Knex.Config = { client: 'sqlite3', connection: { filename: './dev.sqlite3', }, migrations: { directory: 'src/database', }, useNullAsDefault: true, } export default config
Migrations
Agora vamos criar as migrations, as migrations são arquivos de migração no diretório configurado no knexfile.js (ou knexfile.ts), com o objetivo de criar novas tabelas no banco de dados.
- knexfile.ts No arquivo knexfile.ts especificamos o caminho de onde devem ser criado as migrations:
migrations: { directory: "src/database", },
Migration cars
Para criar a migration cars, rode o comando:
npx knex migrate:make create_table_cars -x ts
Na pasta database será criado uma migration cars, cole nela o seguinte código:
import { Knex } from 'knex'
export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable('cars', function (table) {
table.increments()
table.string('name').notNullable()
table.integer('brand_id').notNullable
table.foreign('brand_id').references('brands.id')
})
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTable('cars')
}
Explicando o código
- Este código é uma migração do Knex.js que cria uma tabela "cars" no banco de dados com as colunas "id", "name" e "brand_id", sendo que a última é uma chave estrangeira que referencia a tabela "brands". - A função "up" é executada quando a migração é aplicada no banco de dados, enquanto a função "down" é executada quando a migração é desfeita (rollback).
- A primeira linha do código importa a classe Knex do módulo "knex". Em seguida, é definida a função "up" que recebe um objeto Knex como parâmetro. Essa função utiliza o método "createTable" do objeto "schema" do Knex.js para criar a tabela "cars". O método "createTable" recebe dois argumentos: o nome da tabela e uma função que define as colunas da tabela.
- Dentro da função passada para o método "createTable", é utilizado o método "increments" para criar a coluna "id" com autoincremento. Em seguida, é utilizada a função "string" para criar a coluna "name" do tipo texto que não pode ser nula, e a função "integer" para criar a coluna "brand_id" do tipo inteiro que também não pode ser nula.
- Por fim, é utilizado o método "foreign" para criar uma chave estrangeira na coluna "brand_id" que referencia a coluna "id" da tabela "brands". A referência é especificada com o método "references". A função "down" é definida de forma similar, utilizando o método "dropTable" do objeto "schema" do Knex.js para excluir a tabela "cars" do banco de dados.
Migration brands
Para criar a migration brands, rode o comando:
npx knex migrate:make create_table_brands -x ts
Na pasta database será criado uma migration brands, cole nela o seguinte código:
import { Knex } from 'knex'
export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable('brands', function (table) {
table.increments()
table.string('name').notNullable()
})
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTable('brands')
}
Explicando o código
- Este código é uma migração do Knex.js que cria uma tabela "brands" no banco de dados com as colunas "id" e "name". A função "up" é executada quando a migração é aplicada no banco de dados, enquanto a função "down" é executada quando a migração é desfeita (rollback).
- A primeira linha do código importa a classe Knex do módulo "knex". Em seguida, é definida a função "up" que recebe um objeto Knex como parâmetro. Essa função utiliza o método "createTable" do objeto "schema" do Knex.js para criar a tabela "brands". O método "createTable" recebe dois argumentos: o nome da tabela e uma função que define as colunas da tabela.
- Dentro da função passada para o método "createTable", é utilizado o método "increments" para criar a coluna "id" com autoincremento. Em seguida, é utilizada a função "string" para criar a coluna "name" do tipo texto que não pode ser nula.
- A função "down" é definida de forma similar, utilizando o método "dropTable" do objeto "schema" do Knex.js para excluir a tabela "brands" do banco de dados.
🚨IMPORTANTE:
É importante que as migrations sejam executadas na ordem correta para garantir a integridade do banco de dados. Quando uma migration faz referência a outra, ela deve ser executada após a migration à qual faz referência. Para garantir isso, basta renomear o arquivo da migration para ter um número maior que a outra.
- Como está:
- Correção:
✅ - Está correto, tabela brands que é referenciada pela tabela cars, sendo executada antes da tabela cars
Criando as tabelas no DB
Para criar as tabelas no banco de dados com base nas migrations criadas anteriormente, rode o comando:
npx knex migrate:latest
O arquivo “dev.sqlite3” será criado na raiz do projeto:
TablePlus
TablePlus é um software de gerenciamento de banco de dados que permite aos desenvolvedores e administradores de banco de dados trabalhar com diversos tipos de bancos de dados, como MySQL, PostgreSQL, SQLite, SQL Server e Oracle.
- Clique em adicionar no tableplus
- Escolha SQLIte
- Dê um nome para seu banco de dados
- Em “SQLite file” navegue pela pasta do seu projeto e selecione o arquivo “dev.sqlite3” que foi criado anteriormente.
- Clique em “Save”
- Clique no DB e verifique se as tabelas foram criadas com as colunas especificadas nas migrations.
Controller
Os arquivos controllers são um padrão de arquitetura de software utilizados em aplicações web, em que as requisições HTTP são tratadas por meio de um controlador (controller), que é responsável por receber as requisições HTTP, processá-las e enviar as respostas HTTP apropriadas.
Os arquivos controllers geralmente contêm funções que representam as diferentes operações que podem ser realizadas em um recurso, como listar, criar, atualizar ou deletar.
Acesse o arquivo src/controllers/carsControllers.ts
Insira o seguinte código:
import { Request, Response } from "express"; import knex from "knex"; import config from "../../knexfile"; const knexInstance = knex(config) type Car = { name: string, brand_id: number } const index = async (req: Request, res: Response): Promise<void> => { try { const cars: Cars[] = await knexInstance('cars') .select('cars.name', 'brands.name as brand') .join('brands', 'brands.id', '=', 'cars.brand_id'); res.status(200).send(cars); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } }; export default {index}
Explicando o código
- O código apresentado é um exemplo de um arquivo controller que exporta uma função
index
que trata uma requisição HTTP GET em uma rota específica. Antes da definição da funçãoindex
, o arquivo importa os módulosRequest
eResponse
do pacoteexpress
, bem como o móduloknex
e um objetoconfig
que contém as configurações de conexão com o banco de dados, definidas no arquivoknexfile.js
. - A seguir, o código define um tipo
Cars
, que representa a estrutura dos objetos que serão retornados pela funçãoindex
. Esses objetos possuem duas propriedades:name
(uma string que representa o nome do carro) ebrand_id
(um número que representa o ID da marca do carro). - A função
index
é definida como assíncrona (async
) e recebe dois parâmetros: o objetoreq
(do tipoRequest
) e o objetores
(do tipoResponse
). - Dentro da função
index
, é realizada uma consulta ao banco de dados por meio do objetoknexInstance
, que representa a conexão com o banco de dados configurada anteriormente. O métodoselect
é utilizado para selecionar os camposcars.name
ebrands.name
da tabelacars
, juntamente com o camponame
da tabelabrands
, utilizando o métodojoin
para fazer o join entre as tabelascars
ebrands
, usando a colunabrand_id
da tabelacars
e a colunaid
da tabelabrands
. - O resultado da consulta é armazenado na variável
cars
, que é um array de objetos do tipoCars
. - Se a consulta for bem-sucedida, a função
index
retorna uma resposta HTTP com status 200 e o arraycars
no corpo da resposta. Caso contrário, a função retorna a mensagem de erro no corpo da resposta.
- O código apresentado é um exemplo de um arquivo controller que exporta uma função
Routes
Os arquivos de rotas definem as URLs (Uniform Resource Locators) que serão acessadas pela aplicação e os métodos HTTP que serão usados para cada uma dessas URLs. Eles mapeiam as solicitações recebidas para os métodos correspondentes dos controladores que processarão as solicitações e enviarão as respostas de volta.
Acesse o arquivo src/routes/cars.ts
Insira o seguinte código:
import { Router } from 'express' import carsController from '../controllers/carsControllers' const router: Router = Router() router.get('/', carsController.index) export { router }
Explicando o código
- O código importa o módulo
Router
do pacoteexpress
e o módulocarsController
do arquivo../controllers/carsController
. - Em seguida, cria uma instância de
Router
atribuída à constanterouter
. - O método
get
é chamado no objetorouter
, especificando que, quando uma solicitação GET for recebida na raiz ("/") da aplicação, o controladorcarsController.index
deve ser acionado para lidar com a solicitação. - Por fim, a constante
router
é exportada para ser usada em outros lugares da aplicação. Isso permite que as rotas definidas aqui possam ser usadas em outros arquivos que importem este módulo.
Acesse o arquivo src/routes/index.ts
Insira o seguinte código:
import { router as carsRoutes } from './cars' import { Router } from 'express' const router: Router = Router() router.use('/cars', carsRoutes) export { router }
Explicando o código
- O código importa a constante
carsRoutes
do arquivo./cars
e o móduloRouter
do pacoteexpress
. Em seguida, cria uma instância deRouter
atribuída à constanterouter
. - O método
use
é chamado no objetorouter
, especificando que, quando uma solicitação HTTP for feita no caminho/cars
, a constantecarsRoutes
deve ser usada para lidar com a solicitação. - Por fim, a constante
router
é exportada para ser usada em outros lugares da aplicação. Isso permite que as rotas definidas aqui possam ser usadas em outros arquivos que importem este módulo. Em outras palavras, este módulorouter
é usado para agrupar rotas relacionadas em um único arquivo para tornar a estrutura da aplicação mais organizada e legível.
Index
Acesse o arquivo src/index.ts
Insira o seguinte código:
import express, { Request, Response } from 'express' import { router } from './routes' const app = express() app.use(express.json()) app.use('/api/v1', router) const port = 3000 app.listen(port, () => { console.log(`Listening on ${port}`) })
Explicando o código
- Esse código em TypeScript é um exemplo de como usar o framework Express.js para criar um servidor HTTP básico que responde a requisições de uma API RESTful.
- Na primeira linha, o módulo
express
é importado, assim como as interfacesRequest
eResponse
do pacote. Em seguida, é importado o módulorouter
do arquivoroutes.ts
, que contém a definição das rotas da API. - Depois, uma instância do Express é criada e é adicionado um middleware para fazer o parse do corpo das requisições no formato JSON.
- Em seguida, o middleware
router
é adicionado como o tratador de rotas para todas as requisições que começam com o prefixo/api/v1
. Por fim, o servidor é iniciado na porta3000
e uma mensagem é exibida no console para indicar que o servidor está escutando por requisições na porta especificada.
Módulo 2 - Aprofundando
Até o momento construímos um back end em Node, utilizando o framework Express e como KNEX comunicamos com o banco de dados sqlite3. Criamos uma rota que trata o método GET para exibir todos os carros da nossa loja, agora vamos criar as seguintes funções:
- GET - Exibir somente um carro pelo seu ID
- POST - Inserir um carro
- PUT - Alterar os dados de um carro pelo seu ID
- DEL - Deletar um carro pelo seu ID
- GET - Exibir todas as marcas
- GET - Exibir somente uma marca pelo seu ID
- POST - Inserir uma marca
- PUT - Alterar os dados de uma marca pelo seu ID
- DEL - Deletar de uma marca pelo seu ID
Exibir somente um carro pelo seu ID
Acesse o arquivo src/controllers/carsController.ts
Insira a função:
const show = async (req: Request, res: Response): Promise<void> => { try { const id: number = parseInt(req.params.id); const product: Car[] = await knexInstance('cars') .select('cars.name', 'brands.name as brand') .join('brands', 'brands.id', '=', 'cars.brand_id') .where({ 'cars.id': id }); if (!product.length) throw new Error('Esse carro não existe'); res.status(200).json(product[0]); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } };
Explicando o código
- A função é definida como uma função assíncrona que recebe os objetos Request e Response como parâmetros e retorna uma Promise que não tem um valor de retorno específico (void).
- Dentro da função, a constante "id" é declarada como um número inteiro que é convertido a partir do parâmetro "id" da requisição recebida.
- A constante "product" é definida como um array de objetos Car que é obtido por meio de uma consulta ao banco de dados usando o objeto "knexInstance" do Knex.js. A consulta seleciona os campos "name" da tabela "cars" e "name" da tabela "brands" (como "brand"), unindo as duas tabelas através do campo "id" da tabela "brands" que corresponde à coluna "brand_id" da tabela "cars". A consulta filtra os resultados pela coluna "id" da tabela "cars" que corresponde ao valor da constante "id".
- Se o array "product" não tiver nenhum elemento, a função lança um erro com a mensagem "Esse carro não existe".
- Caso contrário, a resposta da requisição é configurada para ter um status HTTP 200 e retornar o primeiro objeto do array "product".
- Se ocorrer um erro durante a execução da função, a resposta da requisição é configurada para enviar uma mensagem de erro. Se o erro tiver uma propriedade "message", essa mensagem é usada como resposta; caso contrário, o erro é enviado diretamente.
Exporte a função criada no fim do arquivo
export default { index, show, insert }
Acesse o arquivo src/routes/cars.ts e crie a rota get para exibir somente um carro pelo ID
router.get('/:id', carsController.show)
Inserir um carro
Acesse o arquivo src/controllers/carsController.ts
Insira a função:
const insert = async (req: Request, res: Response): Promise<void> => { try { const { name, brand } = req.body; const findBrand = await knexInstance('brands') .select('id') .where({ name: brand }); const brandId: number = findBrand[0].id; const id: number[] = await knexInstance('cars').insert({ name, brand_id: brandId, }); res.status(201).send({ id: id[0], name, brand, }); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } };
Explicando o código
- A função "insert" é uma função assíncrona que recebe um objeto Request e um objeto Response como parâmetros e não retorna nada. Dentro da função, o corpo da requisição é desestruturado para obter os valores de "name" e "brand".
- É feita uma consulta ao banco de dados com o método "select" do knexInstance para verificar se a marca ("brand") já existe na tabela "brands". O resultado da consulta é armazenado em "findBrand", que é um array com os objetos que atendem à condição passada para o método "where".
- É obtido o id da marca ("brandId") do primeiro objeto do array "findBrand". É feita a inserção dos valores de "name" e "brandId" na tabela "cars" utilizando o método "insert" do knexInstance.
- O método "insert" retorna um array com o id do registro inserido
- O objeto Response retorna o status 201 e o objeto com o id, name e brand do carro inserido.
- Em caso de erro, o objeto Response envia uma mensagem de erro ou um objeto com a mensagem de erro.
Exporte a função criada no fim do arquivo
export default { index, show, insert }
Acesse o arquivo src/routes/cars.ts e crie a rota post para adicionar um carro.
router.post('/', carsController.insert)
Alterar os dados de um carro pelo seu ID
Acesse o arquivo src/controllers/carsController.ts
Insira a função:
const update = async (req: Request, res: Response): Promise<void> => { try { const id: number = parseInt(req.params.id); const { name, brand }: { name: string; brand: string } = req.body; const findBrand = await knexInstance('brands') .select('id') .where({ name: brand }); const brandId = findBrand[0].id; const updateCar = { name, brand_id: brandId, }; const car = await knexInstance('cars') .update(updateCar) .where({ id }); if (!car) throw new Error("Esse carro não existe"); res.status(201).send({ id, name, brand }); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } };
Explicando o código
- A função "update" é definida com os parâmetros "req" e "res", representando a requisição e a resposta, respectivamente. Dentro da função, o parâmetro "id" é extraído dos parâmetros da requisição e é convertido para um número inteiro. Em seguida, os parâmetros "name" e "brand" são extraídos do corpo da requisição e tipados como uma string.
- A variável "findBrand" é declarada e aguarda a execução da consulta no banco de dados usando o objeto "knexInstance" para selecionar a coluna "id" da tabela "brands" onde o nome é igual ao valor da variável "brand". A variável "brandId" é declarada e recebe o valor da coluna "id" do primeiro registro retornado pela consulta anterior. A variável "updateCar" é declarada e recebe um objeto com as propriedades "name" e "brand_id" contendo os valores dos parâmetros extraídos anteriormente.
- A variável "car" é declarada e aguarda a execução da consulta no banco de dados usando o objeto "knexInstance" para atualizar a tabela "cars" com os valores contidos na variável "updateCar" onde o "id" é igual ao valor da variável "id". A função "update" retorna o número de registros atualizados. Se a variável "car" for falsa (ou seja, o valor é zero), uma exceção é lançada com a mensagem "Esse carro não existe".
- Caso contrário, a resposta é enviada com o status HTTP 201 e um objeto contendo as propriedades "id", "name" e "brand".
Exporte a função criada no fim do arquivo
export default { index, show, insert, update }
Acesse o arquivo src/routes/cars.ts e crie a rota put para atualizar os dados de um carro.
router.put('/:id', carsController.update)
Deletar um carro pelo seu ID
Acesse o arquivo src/controllers/carsController.ts
Insira a função:
const remove = async (req: Request, res: Response):Promise<void> =>{ try { const id: number= parseInt(req.params.id); const car: number = await knexInstance("cars").delete().where({ id }); if (!car) throw new Error("Esse carro não existe"); res.status(200).json({ msg: "Carro deletado" }); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } }
Explicando o código
- A função remove é definida como uma função assíncrona que recebe dois parâmetros de objeto Request e Response. Dentro da função, uma tentativa é feita para executar o código dentro do bloco try. A variável id é definida como um número inteiro que é convertido a partir do parâmetro id fornecido na solicitação.
- A variável car é definida como o resultado da operação delete() executada na tabela cars usando o ORM knexInstance. A operação delete() remove um registro da tabela que corresponde ao objeto id fornecido. Se a variável car é falsa ou nula, uma exceção é lançada com a mensagem "Esse carro não existe".
- Se a operação delete() é bem-sucedida, uma resposta com o status HTTP 200 é enviada com uma mensagem JSON que diz "Carro deletado".
- Se ocorrer um erro durante a execução do bloco try, o bloco catch será executado. Se a mensagem de erro error.message existir, uma resposta com um objeto JSON contendo a mensagem de erro será enviada. Caso contrário, a própria mensagem de erro será enviada como resposta.
Exporte a função criada no fim do arquivo
export default { index, show, insert, update, remove }
Acesse o arquivo src/routes/cars.ts e crie a rota put para deletar um carro.
router.delete('/:id', carsController.remove)
Rotas das brands
Agora já criamos todo o backend para realizar as ações de um CRUD(Create, read, update e delete) dos carros, precisamos adicionar as mesmas operações para a tabela das marcas. O processo é similar:
Crie um arquivo “brandsController.ts” em src/controllers.
Acesse o arquivo src/controllers/brandsController.ts
Insira o seguinte código:
import { Request, Response } from 'express'; import knex from 'knex'; import config from '../../knexfile'; const knexInstance = knex(config); type Brand = { name: string; brand_id: number; }; const index = async (req: Request, res: Response): Promise<void> => { try { const brands: Brand[] = await knexInstance('brands') .select('*') res.status(200).send(brands); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } }; export default { index };
Acesse a pasta src/routes, crie o arquivo “brands.ts” e insira o código:
import { Router } from 'express' import brandsController from '../controllers/brandsController' const router: Router = Router() router.get('/', brandsController.index) export { router }
Acesse a pasta src/routes, e modifique o arquivo “index.ts”:
import { router as carsRoutes } from './cars' import { router as brandsRoutes } from './brands' import { Router } from 'express' const router: Router = Router() router.use('/cars', carsRoutes) router.use('/brands', brandsRoutes) export { router }
Adicione as funções restantes
Seguindo o mesmo pensamento das funções e rotas dos carros, faremos agora das marcas.
O arquivo “brandsController.ts” localizado em src/controllers, ficará assim:
import { Request, Response } from 'express'; import knex from 'knex'; import config from '../../knexfile'; const knexInstance = knex(config); type Brand = { name: string; brand_id: number; }; const index = async (req: Request, res: Response): Promise<void> => { try { const brands: Brand[] = await knexInstance('brands').select('*'); res.status(200).send(brands); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } }; const show = async (req: Request, res: Response): Promise<void> => { try { const id: number = parseInt(req.params.id); const brand = await knexInstance('brands').select('*').where({ id }); if (!brand.length) throw new Error('Essa marca não existe'); res.status(200).json(brand); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } }; const insert = async (req: Request, res: Response): Promise<void> => { try { const { name } = req.body; const id: number[] = await knexInstance('brands').insert({ name, }); res.status(201).json({ id: id[0], name }); } catch (error: any) { res.send(error); } }; const update = async (req: Request, res: Response): Promise<void> => { try { const id = req.params.id; const { name } = req.body; const updatedData = { name }; const product = await knexInstance('brands') .update(updatedData) .where({ id }); if (!product) throw new Error('Essa marca não existe'); res.status(201).json({ id: id[0], name }); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } }; const remove = async (req: Request, res: Response): Promise<void> => { try { const id: number = parseInt(req.params.id); const product = await knexInstance('brands').delete().where({ id }); if (!product) throw new Error('Essa marca não existe'); res.status(200).json({ msg: 'Marca deletada' }); } catch (error: any) { res.send(error.message ? { error: error.message } : error); } }; export default { index, show, insert, update, remove };
O arquivo “brands.ts” localizado em src/routes, ficará assim:
import { Router } from 'express' import brandsController from '../controllers/brandsController' const router: Router = Router() router.get('/', brandsController.index) router.get('/:id', brandsController.show) router.post('/', brandsController.insert) router.put('/:id', brandsController.update) router.delete('/:id', brandsController.remove) export { router }
O arquivo “index.ts” localizado em src/routes, ficará assim:
import { router as carsRoutes } from './cars' import { router as brandsRoutes } from './brands' import { Router } from 'express' const router: Router = Router() router.use('/cars', carsRoutes) router.use('/brands', brandsRoutes) export { router }
Teste suas requisições com Postman
O Postman é uma ferramenta de colaboração de desenvolvimento de API que permite aos usuários criar, compartilhar, testar e documentar APIs. É uma plataforma completa para desenvolvedores de API que permite enviar solicitações HTTP para um servidor da web e analisar a resposta. O Postman permite criar solicitações HTTP complexas rapidamente, salvar coleções de solicitações e compartilhar essas coleções com outros desenvolvedores. Ele também oferece recursos de colaboração, como documentação de API e compartilhamento de código. O Postman é amplamente utilizado por equipes de desenvolvimento de software em todo o mundo para acelerar o processo de desenvolvimento de APIs e melhorar a eficiência da equipe.
As requisições que fizemos nos carros foram:
GET CAR (VER OS CARROS)
- Selecione o tipo GET na requisição
- Insira a URL: http://localhost:3000/api/v1/cars/
GET CAR BY ID (VER UM CARRO PELO ID)
- Selecione o tipo GET na requisição
- Insira a URL: http://localhost:3000/api/v1/cars/1
No final da URL é onde será inserido o ID do carro que deseja visualizar
POST CAR (INSERIR UM CARRO)
Selecione o tipo POST na requisição
Essa requisição espera receber um objeto JSON com o nome do carro e a marca.
Exemplo:
{ "name": "Palio 2012", "brand": "Fiat" }
PUT CAR (MODIFICAR UM CARRO)
Selecione o tipo PUT na requisição
No final da URL é onde será inserido o ID do carro que deseja modificar
Essa requisição espera receber um objeto JSON com o nome do carro e a marca.
Exemplo:
{ "name": "Hilux", "brand": "Toyota" }
DELETE CAR (DELETAR UM CARRO)
- Selecione o tipo DELETE na requisição
- http://localhost:3000/api/v1/cars/1
- No final da URL é onde será inserido o ID do carro que deseja deletar