Uma simples API REST utilizando Nitro ⚗ + Prisma 🔺 + MongoDB 🍃
Tags: prisma, mongodb, ptbr, node
Published: 2022-11-20 18:40:43

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](http://expressjs.com/pt-br/) ou [NestJS](https://nestjs.com/), 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](https://nuxt.com/) 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](https://unjs.io/), 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](https://github.com/unjs/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](https://nitro.unjs.io/), 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](https://www.prisma.io/).
* O banco de dados será uma instância no [MongoDB](https://www.mongodb.com) criada no [Atlas](https://www.mongodb.com/atlas/database).

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.

```{json}
{
  "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](https://nitro.unjs.io/guide/introduction/typescript).

```{json}
{
  "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](https://nitro.unjs.io/guide/introduction/configuration) do Nitro.

```{ts}
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.

```{ts}
// routes/index.ts
export default defineEventHandler((event) => {
  return "<h1>Hello World</h1>";
});

```
A estrutura de arquivos deve ficar semelhante à imagem abaixo.

![code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lel4u2g572ifupzukqvt.png)

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:

![code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/blbr6koj520uteg4slsp.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hi2i16urid2qeqylpo3l.png)

## 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](https://www.mongodb.com/atlas/database) 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](https://prisma.io) como nosso [ODM](https://pt.wikipedia.org/wiki/Mapeamento_objeto-relacional), 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:

![code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shma4sez14a60j1lf38t.png)

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:

```{js}
// .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.

```{js}
// 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:

```{js}

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](https://www.prisma.io/docs/concepts/components/prisma-schema).

## 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:

```{js}
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:

![code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ffy90po8kp2x6emsgex.png)

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`:

```{js}
// 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](https://developer.mozilla.org/pt-BR/docs/Learn/JavaScript/Asynchronous/Introducing), 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.

```{js}
// 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é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 🤓🖖