15 mins

Criando uma API REST com Next.js, Prisma e PostgreSQL

Next.js vem ganhando força na comunidade de desenvolvimento Web mas pouco tem se falado sobre seu uso para desenvolvimento de API's REST. Neste artigo iremos quebrar esse "tabu" demonstrando a utilização de APIs REST com Next.js, Prisma e PostgreSQL.

Introdução

Neste tutorial, você aprenderá como construir a API REST do backend para uma aplicação de blog chamada "Median" (um simples clone do Medium). Começaremos criando um novo projeto Next.js. Em seguida iniciaremos um servidor PostgreSQL e nos conectaremos a ele usando Prisma.

Ao final, iremos construir a API REST com os seguintes endpoints:

  • POST: /api/articles - Criar um novo artigo.
  • GET: /api/articles - Listar todos os artigos.
  • GET: /api/articles/:id - Listar um artigo específico.
  • PUT: /api/articles/:id - Atualizar um artigo específico.
  • DELETE: /api/articles/:id - Deletar um artigo específico.

Você encontra o código fonte do projeto neste respositório.

Tecnologias utilizadas

Iremos utilizar as seguintes ferramentas para construir esta aplicação:

Pré-requisitos

Conhecimento necessário

Este é um tutorial para iniciantes. No entanto, alguns requisitos precisam ser atendidos:

  • Conhecimento básico de JavaScript ou TypeScript (preferencial)
  • Conhecimento básico de Next.js

Observação: Se você não estiver familiarizado com o Next.js, poderá aprender rapidamente o básico seguindo a seção Getting Started na documentação oficial.

Ambiente de desenvolvimento

Para acompanhar este tutorial, e replicar seu conteúdo esperamos que você:

  • tenha Node.js instalado.
  • tenha o Prisma VSCode Extension instalado. (opcional)
  • tenha acesso a um shell Unix (como o terminal/shell no Linux e macOS) para executar os comandos fornecidos nesta série. (opcional)

Primeira observação: A extensão Prisma VSCode opcional adiciona um IntelliSense muito bom e realce de sintaxe para o Prisma.

Segunda observação: Se você não tiver um shell Unix (por exemplo, se estiver em uma máquina Windows), ainda poderá acompanhar, mas os comandos do shell podem precisar ser modificados para sua máquina.

Criando o projeto Next.js

A primeira etapa do tutorial é criar o projeto Next.js, sendo assim vamos criar as pastas e inicializar o projeto com os comandos a seguir:

$ mkdir building-a-rest-api-with-nextjs-and-prisma
$ cd building-a-rest-api-with-nextjs-and-prisma
$ npx create-next-app@latest --typescript .

Responda aos questionamentos a seguir da seguinte maneira: Yes / No / Yes / Yes / ENTER.

✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use Tailwind CSS with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ Would you like to use experimental `app/` directory with this project? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /Users/dukefs/Workspace/dukescode/building-a-rest-api-with-nextjs-and-prisma.

Using npm.

Initializing project with template: app


Installing dependencies:
- react
- react-dom
- next
- typescript
- @types/react
- @types/node
- @types/react-dom
- eslint
- eslint-config-next


added 273 packages, and audited 274 packages in 18s

104 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Initialized a git repository.

Success! Created building-a-rest-api-with-nextjs-and-prisma at /Users/dukefs/Workspace/dukescode/building-a-rest-api-with-nextjs-and-prisma

Após a instalação das dependências você terá um resultado semelhante a este:

├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── public
│   ├── next.svg
│   └── vercel.svg
├── src
│   └── app
│       ├── api
│       │   └── hello
│       │       └── route.ts
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       ├── page.module.css
│       └── page.tsx
└── tsconfig.json

A maior parte de nosso código ficará contido dentro da pasta src/app/api. É aqui que iremos definir os nossos endpoints e versionamentos para a API.

Podemos iniciar o noso projeto executando:

$ npm run dev

Este comando irá monitorar seus arquivos, recompilando e recarregando automaticamente o servidor sempre que você fizer uma alteração.

Para verificar se o servidor está em execução, acesse a URL http://localhost:3000/api/hello. Você deve ver uma página vazia com a mensagem "Hello, Next.js!".

Atenção: Você deve manter o servidor em execução em segundo plano conforme avança neste tutorial.

Criando uma instância do PostgreSQL

Neste momento iremos criar uma instância do PostgreSQL para utilizar como banco de dados da nossa aplicação. Você pode conferir neste tutorial Como executar o PostgreSQL no Docker com Docker Compose e persistência de dados.

Desta vez iremos utilizar um serviço em cloud que entrega uma instância do PostgreSQL por 24hrs gratuitamente, tempo necessário para realizarmos todos os nossos testes.

Para isso acesse o site Railway e na homepage cliquem em Start a New Project ou acesse diretamente https://railway.app/new.

Selecione a opção Provision PostgreSQL conforme imagem.

provision-postgresql

fig. 1 - opção para provisionamento do postgresql

Basta aguardar que o projeto será criado e a instância do PostgreSQL ficará disponível.

Com a instância do PostgreSQL criada, clique nela e selecione a aba Connect. Nesta seção teremos acesso ao DATABASE_URL que utilizaremos no futuro para conectar utilizando o Prisma.

postgresql-instance-settings

fig. 2 - configurações da instância do postgresql

Configurando o Prisma

Agora que o banco de dados está pronto, é hora de configurar o Prisma!

Para começar, vamos primeiro instalar o Prisma CLI como uma dependência de desenvolvimento. O Prisma CLI permitirá que executemos vários comandos para interagir com o nosso projeto. Para isso execute:

$ npm install -D prisma

E para inicializar o Prisma dentro projeto executamos:

$ npx prisma init

Este comando criará um novo diretório prisma com um arquivo schema.prisma. Este é o arquivo de configuração principal que contém o esquema do banco de dados. Este comando também cria um arquivo .env dentro do nosso projeto.

Atenção: O arquivo .env deve ser adicionado ao .gitignore do projeto.

Vamos agora alterar nosso arquivo .env para definir a nossa string de conexão com o banco de dados.

// .env
DATABASE_URL="postgresql://postgres:OxjXUnj5AjpCvG6wzptJ@containers-us-west-207.railway.app:7089/railway"

Atenção: A sua string de conexão será diferente da minha.

Entendendo o arquivo Schema Prisma

Ao abrir o arquivo prisma/schema.prisma você vai ter um conteúdo semelhante a este:

// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Este arquivo é escrito utilizando Prisma Schema Language, que é um idioma que o Prisma usa para definir o esquema do seu banco de dados. O arquivo schema.prisma tem três componentes principais:

  • Generator: Indica que você deseja gerar o Prisma Client, um type-safe query builder. Ele é usado para enviar consultas ao seu banco de dados.

  • Data source: Especifica sua conexão com o banco de dados. A configuração acima significa que seu provedor de banco de dados é PostgreSQL e a string de conexão do banco de dados está disponível na variável de ambiente DATABASE_URL.

  • Data model: Define seus modelos de banco de dados. Cada modelo será mapeado para uma tabela no banco de daddos. No momento, não há modelos em nosso esquema, iremos explorar essa parte na próxima seção.

Observação: Para mais informações sobre o Prisma Schema você pode consultar a documentação oficial

Modelo de dados

Agora iremos definir o modelo de dados para a nossa API. Para este tutorial, iremos definir apenas um modelo. Article, esse modelo vai representar cada artigo do blog.

Dentro do arquivo prisma/prisma.schema, adicione um novo modelo ao seu esquema chamado Article:

// prisma/schema.prisma

model Article {
  id          Int      @id @default(autoincrement())
  title       String   @unique
  description String?
  body        String
  published   Boolean  @default(false)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

Acabamos de criar o model Article para representar um artigo no banco de dados com vários campos. Cada campo tem um nome (id, título, etc.), um tipo (Int, String, etc.) e outros atributos opcionais (@id, @unique, etc.). Os campos podem se tornar opcionais adicionando ? após o tipo de campo.

O campo id possui um atributo especial chamado @id. Este atributo indica que este campo é a chave primária do modelo. O atributo @default(autoincrement()) indica que esse campo deve ser incrementado automaticamente e atribuído a qualquer registro recém-criado.

O campo published é um sinalizador para indicar se um artigo está publicado ou em modo rascunho. O atributo @default(false) indica que esse campo deve ser definido como falso por padrão.

Os dois campos DateTime, createdAt e updatedAt, rastrearão quando um artigo é criado e quando foi atualizado pela última vez. O atributo @updatedAt atualizará automaticamente o campo com o timestamp atual sempre que um artigo for modificado.

Migration do modelo

Agora que temos o Model criado vamos enviar uma "cópia" para o banco de dados. Este processo é chamado de migration. Durante esse processo o Prismas realiza três processos:

  1. Salva a migração: O Prisma Migrate tirará uma foto da sua estrutura e descobrirá os comandos SQL necessários para realizar a migração. O Prisma salvará o arquivo de migração contendo os comandos SQL na pasta prisma/migrations recém-criada.

  2. Executa a migração: O Prisma Migrate executará o SQL no arquivo de migração para criar as tabelas no seu banco de dados.

  3. Gera o Prisma Client: O Prisma gerará o Prisma Client com base na sua estrutura mais recente. Como você não possui a biblioteca Client instalada, a CLI a instalará para você também. Você deverá ver o pacote @prisma/client nas dependências do seu arquivo package.json. Prisma Client é um construtor de query em TypeScript gerado automaticamente a partir do seu Prisma Schema. Ele é adaptado para o seu Prisma Schema e será usado para enviar consultas ao banco de dados.

Atenção: Você pode aprender mais sobre o Prisma Migrate na documentação oficial.

Vamos então executar a nossa migration com o seguinte comando:

$ npx prisma migrate dev --name "create_table_article"

Se tudo correr bem, você receberá uma mensagem de sucesso semelhante a esta:

Applying migration `20230412173740_create_table_article`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20230412173740_create_table_article/
    └─ migration.sql

Your database is now in sync with your schema.
...
✔ Generated Prisma Client (4.12.0 | library) to ./node_modules/@prisma/client in 132ms

Vamos verificar o arquivo de migração gerado para ter uma visão geral do que o Prisma Migrate realiza nos bastidores.

-- CreateTable
CREATE TABLE "Article" (
    "id" SERIAL NOT NULL,
    "title" TEXT NOT NULL,
    "description" TEXT,
    "body" TEXT NOT NULL,
    "published" BOOLEAN NOT NULL DEFAULT false,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" TIMESTAMP(3) NOT NULL,

    CONSTRAINT "Article_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "Article_title_key" ON "Article"("title");

Atenção: O nome do arquivo de migração pode ser diferente do apresentado neste artigo.

Neste arquivo SQL, encontramos todo o necessário para criar a tabela Article em nosso banco de dados PostgreSQL. Ele foi gerado e executado pelo Prisma baseado no seu Prisma Schema.

Voltando para a nossa instância de PostgreSQL, verificamos que a tabela realmente existe.

table-article-created

fig. 3 - tabela article criada em nosso banco de dados

Criando o CRUD

Finalmente vamos criar o CRUD para o modelo Article.

Antes de efetivamente iniciarmos a criação dos endpoints, vamos criar um singleton para o nosso Prisma Client. Sendo assim crie o arquivo client.ts dentro de src/lib/prisma com o seguinte conteúdo.

// src/lib/prisma/client.ts

import { PrismaClient } from '@prisma/client';

const client = new PrismaClient();

export default client;

Atenção: Os métodos aqui definidos terão validações basicas e tratativas de excepções simples. você pode melhorar isso em algum momento adicionando mais validações.

Criando um Artigo - Create

Excelente agora que temos o nosso client criado, vamos criar o CRUD para o nosso resource Article. Para isso crie o arquivo route.ts dentro da pasta src/app/api/articles.

Vamos adicionar primeiro o endpoit POST: /api/articles para criar um novo Artigo. Abra então o arquivo route.ts e adicione o seguinte:

// src/app/api/articles/route.ts

import client from '@/lib/prisma/client';
import { Article } from '@prisma/client';
import { NextResponse, NextRequest } from 'next/server';

export async function POST(request: NextRequest) {
	const newArticle: Article = await request.json();
	const createdArticle: Article = await client.article.create({
		data: newArticle,
	});

	return new NextResponse(JSON.stringify(createdArticle), {
		status: 201,
		statusText: 'Created',
	});
}

Perceba que neste momento apenas realizamos os imports do nosso client para se comunicar com o banco de dados, do nosso model Article apenas para tipar a nossa variáveis newArticle e createdArticle e do NextRequest e NextResponse para tratar as requisições e respostas HTTP's.

Criamos a função asyncrona POST para criar um novo artigo. Nela obtemos o body do NextRequest e passamos como parâmetro a variável newArticle. A variável createdArticle recebe o retorno da chamada ao nosso cliente prisma por meio client.article.create e passa como parâmetro a variável newArticle.

Ao final retornamos o NextResponse com o status 201 created e o nosso Artigo recém persistido no banco de dados.

Para relizar um teste basta fazermos uma chamada HTTP POST para nossa api como a seguir.

request-http-post

fig. 3 - exemplo de chamada http post para a api

Perceba que a quantidade de informações recebidas é maior que a quantidade de informações enviadas. Podemos concluir que as informações adicionais vieram do nosso banco de dados.

Obtendo um ou mais Artigos - GET

Vamos agora obter uma lista de Artigos, para isso vamos continuar editando o nosso arquivo route.ts. Abaixo, vamos criar o endpoint GET: /api/articles para obter uma lista de Artigos.

// src/app/api/articles/route.ts

...

export async function GET() {
	const articles: Article[] = await client.article.findMany();

	return new NextResponse(JSON.stringify(articles), {
		status: 200,
		statusText: 'OK',
	});
}

Desta vez apenas definimos uma função assincrona GET para obter uma lista de Artigos. Utilizamos novamente nosso client e por meio da função client.article.findMany obtemos uma lista de Artigos.

Retornamos essa lista de Artigos juntamento com o status 200 ok.

request-http-get

fig. 4 - exemplo de chamada http get para a api listando os artigos

Para obter um artigo específico por meio do seu ID, vamos criar o arquivo route.ts dentro da pasta src/app/api/articles/[id] e insirir o seguinte conteúdo:

// src/app/api/articles/[id]/route.ts

import client from '@/lib/prisma/client';
import { Article } from '@prisma/client';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
import { NextResponse, NextRequest } from 'next/server';

type FindById = {
	id: string;
};

export async function GET(request: NextRequest, context: { params: FindById }) {
	try {
		const article: Article = await client.article.findUniqueOrThrow({
			where: {
				id: Number(context.params.id),
			},
		});

		return new NextResponse(JSON.stringify(article), {
			status: 200,
			statusText: 'OK',
		});
	} catch (error) {
		const msgError = (error as PrismaClientKnownRequestError).message;

		return new NextResponse(JSON.stringify({ message: msgError }), {
			status: 404,
			statusText: 'Not Found',
		});
	}
}

Aqui novamente realizamos os imports necessários para tratar as requisições e respostas HTTP's. Veja também que definimos um type, FindById para representar o id do artigo passado como parâmetro na url.

Finalmente utilizamos o nosso Prisma Client para obter o artigo específico por meio do seu ID utilizando o client.article.findUniqueOrThrow. Tratamos a exception que ele pode lançar caso não encontre o Artigo solicitado.

request-http-get-one

fig. 4 - exemplo de chamada http get para a api filtrando por ID

request-http-get-not-found

fig. 5 - exemplo de chamada http get para a api filtrando por ID inexistente

Alterando um Artigo - PUT

Dando sequência ao nosso CRUD vamos criar agora o endpoint PUT: /api/articles/[id] para alterar um artigo. Para isso vamos continuar editando o nosso arquivo route.ts contido na pasta src/app/api/articles/[id]. Adicione o seguinte conteúdo:

// src/app/api/articles/[id]/route.ts

...

export async function PUT(request: NextRequest, context: { params: FindById }) {
	const newArticleData: Article = await request.json();

	try {
		const updatedArticle: Article = await client.article.update({
			where: {
				id: Number(context.params.id),
			},
			data: newArticleData,
		});

		return new NextResponse(JSON.stringify(updatedArticle), {
			status: 200,
			statusText: 'OK',
		});
	} catch (error) {
		const msgError = (error as PrismaClientKnownRequestError).meta?.cause;

		return new NextResponse(JSON.stringify({ message: msgError }), {
			status: 404,
			statusText: 'Not Found',
		});
	}
}

Novamente obtemos o ID do nosso artigo pelo parametro context.params.id e as atualizações a serem realizadas no artigo pelo body da requisição utilizando o await request.json().

Feito isso, vamos utilizar o client.article.update para atualizar o artigo. Realizamos também uma tratativa de erro que pode ser lançada caso não encontre o artigo solicitado.

Ao final retornamos o artigo atualizado com o status 200 ok, ou a mensagem de erro caso não encontre o artigo solicitado com o status 404 not found.

request-http-put-success

fig. 6 - exemplo de chamada http put para a api filtrando por ID

request-http-put-not-found

fig. 7 - exemplo de chamada http get para a api filtrando por ID inexistente

Deletando um Artigo - DELETE

Estamos chegando na reta final do nosso artigo. Vamos agora implementar o endpoint DELETE: /api/articles/[id] para deletar um artigo. Para isso vamos continuar deletando o arquivo route.ts contido na pasta src/app/api/articles/[id]. Adicione o seguinte conteúdo:

// src/app/api/articles/[id]/route.ts

...

export async function DELETE(
	request: NextRequest,
	context: { params: FindById }
) {
	try {
		await client.article.delete({
			where: {
				id: Number(context.params.id),
			},
		});

		return new NextResponse(null, {
			status: 204,
			statusText: 'No Content',
		});
	} catch (error) {
		const msgError = (error as PrismaClientKnownRequestError).meta?.cause;

		return new NextResponse(JSON.stringify({ message: msgError }), {
			status: 404,
			statusText: 'Not Found',
		});
	}
}

Desta vez, vamos utilizar o client.article.delete para deletar o artigo. Realizamos também uma tratativa de erro que pode ser lançada caso não encontre o artigo solicitado.

Nossos retornos possíveis são:

  • 204 - Artigo deletado com sucesso.
  • 404 - Artigo não encontrado.

request-http-delete-success

fig. 8 - exemplo de chamada http delete para a api filtrando por ID

request-http-delete-not-found

fig. 9 - exemplo de chamada http delete para a api filtrando por ID inexistente

Chegamos ao final do nosso CRUD, lembre-se que você pode adicionar mais métodos de consulta, validações e tratamentos de erros.

Conclusão

Neste artigo criamos uma API REST com um CRUD completo para fazer a gestão de Artigos. Com métodos para listar todos os Artigos, obter um artigo específico por meio do seu id, alterar um artigo específico por meio do seu id e excluir um artigo específico por meio do seu id.

Apresentamos como criar um Schema com o Prisma e como realizar o migration para que a tabela fosse criada no banco de dados, além é claro apresentamos como criar uma instância do PostgreSQL gratuitamente por 24hrs utilizando o Railway.

Podemos concluir que a combinação de Next.js, Prisma e PostgreSQL oferece uma stack eficiente e poderosa para criar uma API REST de alto desempenho. Com a facilidade de desenvolvimento do Next.js, a flexibilidade do Prisma e a capacidade robusta do PostgreSQL, é possível desenvolver APIs escaláveis e confiáveis para atender às necessidades de negócios e dos usuários finais.

Portanto, se você está procurando uma stack confiável para construir sua próxima API, a combinação de Next.js, Prisma e PostgreSQL é definitivamente uma opção que vale a pena considerar.


E é isso por hoje, pessoal!

Chegamos ao final de mais um artigo, espero que tenha sido útil e que você tenha aprendido algo novo.

Caso tenha alguma dúvida, comentário ou tenha encontrado algum erro, por favor, envie-me um email. Ficarei feliz em ouvir de você.

Se desejar receber novos artigos diretamente em seu e-mail, por favor, assine a nossa Newsletter. E se você já é um assinante, muito obrigado!

Aproveito e deixo um convite para nos conectarmos no Twitter e LinkedIn.

👋 Obrigado por ler até o final e até o próximo artigo !!!

Turbine a sua caixa de entrada

Junte-se a mais de 0 desenvolvedores inscritos

Talvez você possa curtir