kb.erickguedes.com
Node.js: Backend JavaScript em Produção

Banco de Dados e Autenticação

Aula 4 de 7

ORM/ODM

Prisma (SQL)

npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

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

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String
  password  String
  role      Role     @default(USER)
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
}

enum Role {
  USER
  ADMIN
}
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function main() {
  // Criar
  const user = await prisma.user.create({
    data: {
      email: '[email protected]',
      name: 'Alice',
      password: 'hash123'
    }
  });

  // Buscar com relação
  const users = await prisma.user.findMany({
    where: { role: 'ADMIN' },
    include: { posts: true },
    orderBy: { createdAt: 'desc' }
  });

  // Atualizar
  await prisma.user.update({
    where: { id: 1 },
    data: { name: 'Alice Updated' }
  });

  // Paginação
  const page = await prisma.user.findMany({
    skip: 0,
    take: 10,
    cursor: { id: 1 }
  });
}

main().finally(() => prisma.$disconnect());

Migrations

npx prisma migrate dev --name init
npx prisma migrate deploy
npx prisma db push
npx prisma studio

Sequelize

const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize(process.env.DATABASE_URL);

const User = sequelize.define('User', {
  email: { type: DataTypes.STRING, unique: true, allowNull: false },
  name: { type: DataTypes.STRING, allowNull: false },
  password: { type: DataTypes.STRING, allowNull: false },
  role: { type: DataTypes.ENUM('USER', 'ADMIN'), defaultValue: 'USER' }
});

const Post = sequelize.define('Post', {
  title: { type: DataTypes.STRING, allowNull: false },
  content: { type: DataTypes.TEXT },
  published: { type: DataTypes.BOOLEAN, defaultValue: false }
});

User.hasMany(Post, { foreignKey: 'authorId' });
Post.belongsTo(User, { foreignKey: 'authorId' });

await sequelize.sync({ alter: true });

Mongoose (MongoDB)

const mongoose = require('mongoose');

await mongoose.connect(process.env.MONGODB_URI);

const userSchema = new mongoose.Schema({
  email: { type: String, unique: true, required: true },
  name: { type: String, required: true },
  password: { type: String, required: true },
  role: { type: String, enum: ['USER', 'ADMIN'], default: 'USER' }
}, { timestamps: true });

const User = mongoose.model('User', userSchema);

const user = await User.create({
  email: '[email protected]',
  name: 'Alice',
  password: 'hash123'
});

const users = await User.find({ role: 'ADMIN' }).populate('posts');

JWT (JSON Web Token)

npm install jsonwebtoken bcrypt
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

const SECRET = process.env.JWT_SECRET || 'super-secret';

// Hash de senha
async function hashPassword(password) {
  const salt = await bcrypt.genSalt(12);
  return bcrypt.hash(password, salt);
}

// Comparar senha
async function comparePassword(password, hash) {
  return bcrypt.compare(password, hash);
}

// Gerar token
function generateToken(payload) {
  return jwt.sign(payload, SECRET, { expiresIn: '15m' });
}

// Gerar refresh token
function generateRefreshToken(payload) {
  return jwt.sign(payload, SECRET + '-refresh', { expiresIn: '7d' });
}

// Verificar token
function verifyToken(token) {
  return jwt.verify(token, SECRET);
}

Login e Refresh Token

const express = require('express');
const router = express.Router();

router.post('/login', async (req, res) => {
  const { email, password } = req.body;

  const user = await prisma.user.findUnique({ where: { email } });
  if (!user) {
    return res.status(401).json({ error: 'Credenciais inválidas' });
  }

  const valid = await comparePassword(password, user.password);
  if (!valid) {
    return res.status(401).json({ error: 'Credenciais inválidas' });
  }

  const payload = { userId: user.id, role: user.role };
  const token = generateToken(payload);
  const refreshToken = generateRefreshToken(payload);

  res.json({ token, refreshToken, user: { id: user.id, name: user.name, email: user.email } });
});

router.post('/refresh', (req, res) => {
  const { refreshToken } = req.body;

  try {
    const decoded = jwt.verify(refreshToken, SECRET + '-refresh');
    const newToken = generateToken({ userId: decoded.userId, role: decoded.role });
    res.json({ token: newToken });
  } catch {
    res.status(401).json({ error: 'Refresh token inválido' });
  }
});

RBAC Middleware

function authMiddleware(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    return res.status(401).json({ error: 'Token não fornecido' });
  }

  const token = authHeader.split(' ')[1]; // Bearer <token>

  try {
    const decoded = verifyToken(token);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Token inválido ou expirado' });
  }
}

function requireRole(...roles) {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Acesso não autorizado' });
    }
    next();
  };
}

router.get('/admin/users', authMiddleware, requireRole('ADMIN'), async (req, res) => {
  const users = await prisma.user.findMany();
  res.json(users);
});

router.put('/posts/:id', authMiddleware, async (req, res) => {
  const post = await prisma.post.findUnique({ where: { id: Number(req.params.id) } });

  if (!post) return res.status(404).json({ error: 'Post não encontrado' });

  // Verificar se é o autor ou admin
  if (post.authorId !== req.user.userId && req.user.role !== 'ADMIN') {
    return res.status(403).json({ error: 'Sem permissão' });
  }

  const updated = await prisma.post.update({
    where: { id: post.id },
    data: req.body
  });
  res.json(updated);
});

Lab: Exercício - API com Autenticação e RBAC

// index.js
const express = require('express');
const prisma = require('./prisma');
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');
const { authMiddleware } = require('./middleware/auth');

const app = express();
app.use(express.json());

app.use('/auth', authRoutes);
app.use('/users', authMiddleware, userRoutes);

app.listen(3000);
# Testar fluxo completo
curl -X POST http://localhost:3000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"123456","name":"Admin"}'

curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"123456"}'

# Usar token nas requisições
TOKEN="eyJhbGciOiJIUzI1NiIs..."
curl http://localhost:3000/users \
  -H "Authorization: Bearer $TOKEN"

Sempre use bcrypt com salt rounds >= 12 para hash de senhas. Tokens JWT devem ter expiração curta (15min) combinados com refresh tokens de longa duração (7d). Prisma é a escolha moderna para ORM com type-safety e migrations.