Ao pesquisar sobre construir APIs ou webservices utilizando JavaScript/Node.js é comum encontrar diversos tutoriais utilizando frameworks (além de guias, utilizando os módulos nativos do Node) como Express ou NestJS, ferramentas sólidas e bastante utilizadas na comunidade. É de se esperar, considerando o tamanho que o ecossistema JavaScript, que não ficaríamos limitados a apenas duas ferramentas, certo?
Recentemente, o desenvolvimento do Nuxt 3 tem chamado bastante a minha atenção, não só por ser o framework fullstack do Vue (quem me conhece pessoalmente, sabe que defendo o Vue com unhas e dentes), mas principalmente pela forma que o time escolheu para desenvolver as bases do framework.
UnJS
O time do Nuxt criou organização, o UnJS, responsável por criar e manter diversos pacotes que são "universais" e agnósticos de qualquer framework e/ou ambiente. Juntos, muitos desses pacotes formam a base do Nuxt 3.
H3 e Nitro
Sendo um framework fullstack, o Nuxt oferece a possibilidade de criar um servidor http dentro do projeto, o H3 é responsável por isso. Quando utilizado isoladamente, o H3 é muito similar ao express, mas no lugar dos callbacks é utilizado um sistema próprio de eventos.
O Nitro, que vamos utilizar nesse tutorial, combina o servidor http do H3 com algumas outras bibliotecas do UnJS que nos dão algumas features interessantes, como autoimports das bibliotecas instaladas e um sistema de rotas baseado em arquivos (como encontramos em frameworks como Next.js ou no próprio Nuxt).
O Nitro é o servidor http que é gerado automaticamente na pasta server
de um projeto Nuxt 3.
O que vamos construir hoje?
Criaremos do zero uma API REST de uma biblioteca, gerenciando o CRUD de Livros, Autores e Gêneros.
- Utilizaremos o Nitro como servidor HTTP.
- Nosso ORM será o Prisma.
- O banco de dados será uma instância no MongoDB criada no Atlas.
Como o Prisma suporta diversos bancos de dados relacionais, sinta-se a vontade para utilizar outras opções se preferir.
Criando um projeto Node e instalando o Nitropack
Vamos criar uma pasta, e iniciar um projeto Node.js dentro dela.
mkdir books-nitro-api
cd books-nitro-api
npm init -y
Ainda com o terminal aberto, vamos instalar o nitropack como dependência de desenvolvimento utilizando o comando abaixo.
npm install --dev nitropack
Abrindo a pasta no nosso editor de código, podemos inserir os scripts do nitro no nosso package.json
. O resultado deve ser parecido com o código abaixo.
{
"name": "books-nitro-api",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"dev": "nitro dev",
"build": "nitro build"
},
"devDependencies": {
"nitropack": "^1.0.0"
}
}
O TypeScript é gerenciado de forma automática pelo Nitro.
Vamos criar o arquivo tsconfig.json
na raiz do nosso projeto, e adicionar o seguinte conteúdo como indicado na documentação.
{
"extends": "./.nitro/types/tsconfig.json"
}
As pastas .nitro
e .output
são geradas automaticamente assim que rodamos o projeto.
Finalizando a etapa de configuração, vamos adicionar o arquivo nitro.config.ts
e inserir o conteúdo abaixo que corresponde à função de configuração do Nitro.
import { defineNitroConfig } from 'nitropack'
export default defineNitroConfig({})
Vamos criar a nossa primeira rota, como dito anteriormente, o Nitro possui um sistema baseado em arquivos para definir suas rotas. Na raiz vamos criar a pasta router
e criar o arquivo index.ts
que vai corresponder a nossa rota /
do servidor.
Dentro do arquivo index.ts
vamos definir nosso primeiro evento, assim como sua biblioteca base, o H3, o Nitro é baseado em eventos.
// routes/index.ts
export default defineEventHandler((event) => {
return "<h1>Hello World</h1>";
});
A estrutura de arquivos deve ficar semelhante à imagem abaixo.
Inicie o servidor de desenvolvimento utilizando o comando abaixo:
npm run dev
O terminal deve exibir as URLs para acessar o servidor, como na imagem abaixo:
Observe que as pastas .nitro
e .output
foram geradas automaticamente.
Ao digitar a URL fornecida no navegador, temos o retorno que escrevemos no arquivo routes/index.ts
MongoDB
Não abordaremos em detalhes sobre a criação de um banco de dados com MongoDB neste tutorial.
Abra uma nova guia, crie uma conta no MongoDB Atlas e volte aqui quando já tiver uma string
de conexão com o banco.
...
Já voltou? Seguindo para a instalação do Prisma!
Instalando o Prisma
Vamos utilizar o Prisma como nosso ODM, uma espécie de tradutor entre o banco de dados e a linguagem de programação.
Vamos instalar o Prisma como dependência de desenvolvimento, utilizando o comando abaixo:
npm install prisma --save-dev
Logo em seguida, vamos instanciar os arquivos do Prisma utilizando o comando abaixo:
npx prisma init --datasource-provider mongodb
Você vai perceber que houve mudanças nos arquivos do nosso projeto, o comando anterior criou uma pasta chamada prisma
e um arquivo .env
que corresponde à configuração de ambiente da aplicação.
A estrutura de arquivos deve parecer com a imagem abaixo:
Dentro do arquivo .env
haverá uma propriedade chamada DATABASE_URL
, é aqui que vamos colar a string de conexão com o MongoDB, similar ao snippet abaixo:
// .env
DATABASE_URL="mongodb+srv://USERNAME:PASSWORD@HOST:PORT/DATABASE"
Abrindo o arquivo prisma/schema.prisma
, temos algumas informações já inseridas automaticamente pelo Prisma, como o client (que vamos instalar nos próximos passos), o banco de dados, MongoDB no nosso caso, e uma url que importa a nossa variável de ambiente que vai conectar a nossa API ao banco de dados na nuvem.
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
Aqui vamos criar um modelo básico de livro com os campos name
, ìsbn
e description
, além de outros campos relacionados ao banco. Vamos adicionar o seguinte trecho de código:
model Book {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
isbn String @unique
description String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
-
model Book
- aqui informamos ao schema que criaremos uma coleção de documentos, em um banco relacional o Prisma cria uma tabela. -
id String @id @default(auto()) @map("_id") @db.ObjectId
- cada documento do MongoDB possui um id único do tipo String, o que significam as notações com @ no final da linha. Essas notações variam de banco para banco dentro do Prisma. - Declaramos os atributos name, isbn e description do tipo String, atenção para a notação
@unique
que inserimos no campo isbn, indicando que esse valor não pode se repetir e é único para cada documento. - Os campos createdAt e updatedAt não são obrigatórios, mas é interessante saber quando o registro foi criado e atualizado pela última vez, as notações são padrão do Prisma.
- Outras informações relevantes podem ser encontradas na documentação.
Instalando o Prisma Client
O Prisma Client é a parte do Prisma responsável por gerenciar o banco de dados e gerar a tipagem baseada no schema que criamos no passo anterior. Instale a biblioteca com o seguinte comando:
npm install @prisma/client
Após instalar a biblioteca, é hora de gerar as tipagens específicas que o TypeScript do nosso projeto vai utilizar, a cada modificação do schema devemos rodar o comando
npx prisma generate dev
Instanciando cliente do Prisma
Em toda a nossa aplicação, devemos ter apenas um cliente do Prisma gerenciando todas as nossas requisições.
Crie uma pasta utils
na raiz do nosso projeto e dentro dela um arquivo prisma.ts
com o seguinte conteúdo:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export { prisma };
Criando as rotas da API
Baseado em eventos e com o sistema de rotas baseado em arquivos, o Nitro possui uma organização de arquitetura um pouco diferente de outros frameworks.
Dentro da pasta routes
crie em seguida as pastas api
, e a seguinte estrutura de arquivos:
Observe que o método da nossa requisição http é declarado no arquivo.
Ambos os arquivos vão responder pela mesma URL na API, http://minhapi/api/books
, mas um com o método GET
e outro com o método POST
.
(Saiba mais sobre os métodos HTTP)[https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Methods]
Iniciando pelo código do método GET
:
// routes/api/books.get.ts
import { prisma } from "../../utils/prisma";
export default defineEventHandler(async (event) => {
const books = await prisma.book.findMany();
return books;
});
- Na primeira linha importamos o cliente do Prisma que instanciamos no passo anterior.
- Logo em seguida exportamos por padrão em toda rota do Nitro a função do
defineEventHandler
do H3, o Nitro vai gerenciar a importação do H3 por padrão, sem precisar declarar explicitamente. - A função
defineEventHandler
recebe por parâmetro uma função anônima que expõe o evento que tanto falamos desde o início do post e que vai ser mais útil quando comentarmos sobre o método POST. - Como a função anônima que passamos por parâmetro é assíncrona, veja mais sobre assincronismo no JS, definimos uma constante
books
e esperamos que o nosso cliente do Prisma retorne todos os livros da API através do métodofindMany()
. - Por fim, retornamos o resultado para o cliente que está solicitando os dados a nossa API.
// routes/api/books.post.ts
import { prisma } from "../../utils/prisma";
export default defineEventHandler(async (event) => {
const payload = await readBody(event);
const newBook = await prisma.book.create({ data: payload });
return newBook;
});
- Seguindo a mesma estrutura padrão do anterior, mas observe que na constante
payload
eu chamo outro método do H3 e passo o meu evento como parâmetro. Todos os dados da nossa requisição estão guardados dentro do evento. Nesse caso estamos lendo o body que mandamos por requisição com os dados do livro a ser cadastrado. - Na constante
newBook
utilizamos o métodocreate()
do Prisma para inserir um livro no banco de dados, e retornamos ele na linha seguinte.
Então, esse é o tutorial de uma API bem simples utilizando essa stack. Talvez eu volte aqui em outros posts para falar sobre mais detalhes que ficaram de fora desse guia (rotas mais complexas, tratamento de erros, manipulação de eventos, middlewares...), até a próxima 🤓🖖
Top comments (0)