DEV Community

Cover image for Uma simples API REST utilizando Nitro ⚗ + Prisma 🔺 + MongoDB 🍃
Sidney Alex
Sidney Alex

Posted on • Updated on

Uma simples API REST utilizando Nitro ⚗ + Prisma 🔺 + MongoDB 🍃

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
Enter fullscreen mode Exit fullscreen mode

Ainda com o terminal aberto, vamos instalar o nitropack como dependência de desenvolvimento utilizando o comando abaixo.

npm install --dev nitropack
Enter fullscreen mode Exit fullscreen mode

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"
  }
}

Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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({})
Enter fullscreen mode Exit fullscreen mode

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>";
});

Enter fullscreen mode Exit fullscreen mode

A estrutura de arquivos deve ficar semelhante à imagem abaixo.

code

Inicie o servidor de desenvolvimento utilizando o comando abaixo:

npm run dev
Enter fullscreen mode Exit fullscreen mode

O terminal deve exibir as URLs para acessar o servidor, como na imagem abaixo:

code
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

code

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
Enter fullscreen mode Exit fullscreen mode

Logo em seguida, vamos instanciar os arquivos do Prisma utilizando o comando abaixo:

npx prisma init --datasource-provider mongodb
Enter fullscreen mode Exit fullscreen mode

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:

code

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"
Enter fullscreen mode Exit fullscreen mode

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")
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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:

code

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;
});

Enter fullscreen mode Exit fullscreen mode
  • 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étodo findMany().
  • 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;
});

Enter fullscreen mode Exit fullscreen mode
  • 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étodo create() 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)