Neste guia completo, você aprenderá como construir uma API segura e escalável com autenticação baseada em JWT utilizando Fastify, ZenStack e Prisma. Abordaremos desde a modelagem de dados com validação e controle de acesso (ACL) até a criação de endpoints personalizados para registro e login, com proteção contra acesso não autorizado.
Etapa 1 – Inicialização e Instalação do Projeto
Comece configurando o ambiente com as bibliotecas necessárias:
npm init -y
# Dependências principais
npm install fastify @zenstackhq/server @zenstackhq/runtime @prisma/client fastify-zod zod bcryptjs jsonwebtoken
# Dependências de desenvolvimento
npm install -D prisma ts-node-dev typescript @types/node @types/jsonwebtoken
Iniciando o ZenStack e Prisma
npx zenstack init
npx zenstack generate
npx prisma db push
Isso criará arquivos como schema.zmodel, que é a base da validação declarativa e política de acesso (ACL) do ZenStack.
Etapa 2 – Modelagem com ZModel + Regras de Validação e Autorização
Edite o arquivo schema.zmodel para definir a estrutura da tabela User com validação automática:
datasource db { provider = "sqlite"; url = "file:./dev.db" }
generator client { provider = "prisma-client-js" }
model User {
id String @id @default(cuid())
email String @unique @email
password String @length(min: 8)
createdAt DateTime @default(now())
@@allow('read', auth() != null && auth().id == id)
@@allow('create', auth() == null)
}
Explicação:
- Validação declarativa com
@emaile@length(min: 8)evita dados inválidos na criação. - Controle de acesso (ACL):
- Apenas o próprio usuário pode ler seus dados.
- Apenas usuários anônimos podem criar contas (ex:
/register).
Após salvar:
npx zenstack generate
npx prisma db push
Etapa 3 – Configurando Fastify com Prisma + ZenStack
Crie o arquivo src/app.ts:
import Fastify from 'fastify';
import { PrismaClient } from '@prisma/client';
import { enhance } from '@zenstackhq/runtime';
import { ZenStackFastifyPlugin } from '@zenstackhq/server/fastify';
import fastifyJwt from '@fastify/jwt';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
const app = Fastify();
app.register(fastifyJwt, { secret: process.env.JWT_SECRET || 'supersecret' });
// Plugin ZenStack – fornece API REST/RPC para models
app.register(ZenStackFastifyPlugin, {
prefix: '/api/model',
getPrisma: (req) => {
const user = req.user as { id: string } | null;
return enhance(prisma, { user });
},
});
Etapa 4 – Endpoints personalizados: Registro e Login
POST /register
app.post('/register', async (req, res) => {
const { email, password } = req.body as { email: string; password: string };
const hashed = await bcrypt.hash(password, 10);
try {
const user = await enhance(prisma, { user: null }).user.create({
data: { email, password: hashed },
});
const token = app.jwt.sign({ id: user.id });
return res.status(201).send({ token });
} catch (err: any) {
return res.status(400).send({ error: err.message });
}
});
enhance(prisma, { user: null })ignora contexto de autenticação (necessário no registro).- A validação do e-mail e da senha é aplicada automaticamente com base nas regras do ZModel.
POST /login
app.post('/login', async (req, res) => {
const { email, password } = req.body as { email: string; password: string };
const user = await prisma.user.findUnique({ where: { email } });
if (!user) return res.status(401).send({ error: 'Credenciais inválidas' });
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) return res.status(401).send({ error: 'Credenciais inválidas' });
const token = app.jwt.sign({ id: user.id });
return res.send({ token });
});
Etapa 5 – Middleware de Autenticação com JWT
Adicione suporte a autenticação automática via JWT:
app.decorateRequest('user', null);
app.addHook('onRequest', async (req, res) => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (token) {
const decoded = await app.jwt.verify(token);
req.user = { id: (decoded as any).id };
}
} catch {
// Token inválido: prossegue sem autenticação
}
});
Com isso, o ZenStack consegue aplicar regras como auth().id == id de forma automática no contexto das requisições.
Etapa 6 – Testando com curl ou Insomnia/Postman
Registro de usuário:
curl -X POST http://localhost:3000/register \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"senha123"}'
Login do usuário:
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"senha123"}'
Consulta protegida (com ACL):
curl -X POST http://localhost:3000/api/model/user/findUnique \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <SEU_TOKEN>" \
-d '{"where":{"id":"<ID_USER>"}}'
Se o id requisitado não for o do usuário autenticado, a requisição será automaticamente negada com status 403, graças à política declarativa do ZenStack.
Conclusão
Com essa abordagem, você construiu uma base sólida para autenticação e autorização moderna com:
- Modelos seguros usando ZenStack com validação integrada e ACL.
- Endpoints personalizados para
registerelogin, utilizando JWT. - Autorização automática com
enhance(prisma)no contexto do usuário. - API CRUD pronta via
/api/model/*com proteção embutida.
Essa arquitetura é modular, extensível e preparada para produção. Você pode expandi-la com refresh tokens, roles, verificação de e-mail, atualização de perfil e muito mais.