Guides
Fundamentos ▾
Versionamento ▾
Deploy ▾

Express.js

Framework web minimalista para Node.js. Roteamento, middleware, HTTP. Base para APIs REST.

O que é

Framework web minimalista para Node.js. Roteamento, middleware, suporte a HTTP. Base para a maioria das APIs REST em Node.

npm install express
npm install -D @types/express  # TypeScript

Hello World

import express from 'express'

const app = express()
const PORT = process.env.PORT ?? 3000

app.use(express.json())

app.get('/', (req, res) => {
  res.json({ message: 'Hello World' })
})

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`)
})

Request (req)

app.get('/users/:id', (req, res) => {
  req.params.id          // rota paramétrica
  req.query.page         // ?page=2
  req.body               // JSON body (requer express.json())
  req.headers['authorization']
  req.method             // GET, POST, etc.
  req.path               // /users/42
  req.url                // /users/42?page=1
  req.ip                 // IP do cliente
  req.protocol           // http / https
  req.hostname           // api.example.com
  req.get('Content-Type')// pegar header
  req.cookies            // requer cookie-parser
})

Response (res)

// status + json
res.status(200).json({ data: users })
res.status(201).json({ id: newUser.id })
res.status(204).send()         // No Content
res.status(404).json({ error: 'Not found' })

// outros tipos
res.send('texto simples')
res.sendFile('/path/to/file.pdf')
res.redirect(301, '/nova-url')

// headers
res.set('X-Custom-Header', 'valor')
res.cookie('session', token, { httpOnly: true, secure: true })
res.clearCookie('session')

Métodos HTTP

app.get('/users', handler)
app.post('/users', handler)
app.put('/users/:id', handler)
app.patch('/users/:id', handler)
app.delete('/users/:id', handler)
app.all('/path', handler)     // todos os métodos

Parâmetros

// rota paramétrica
app.get('/users/:id', (req, res) => {
  const { id } = req.params  // string
})

// múltiplos parâmetros
app.get('/posts/:postId/comments/:commentId', (req, res) => {
  const { postId, commentId } = req.params
})

// parâmetro opcional
app.get('/users/:id?', handler)

// wildcard
app.get('/files/*', handler)

// query string: GET /users?page=2&limit=10&role=admin
app.get('/users', (req, res) => {
  const page = parseInt(req.query.page) || 1
  const limit = parseInt(req.query.limit) || 10
})

Router

// src/routes/users.js
import { Router } from 'express'
const router = Router()

router.get('/', getUsers)
router.get('/:id', getUserById)
router.post('/', createUser)
router.put('/:id', updateUser)
router.delete('/:id', deleteUser)

export default router

// src/index.js
import usersRouter from './routes/users.js'
app.use('/api/users', usersRouter)
// agora: GET /api/users, GET /api/users/:id, etc.

Middleware

Funções com acesso a req, res e next. Executadas em sequência na cadeia.

1
Request
chegou do client
2
Logger
console.log
3
Auth
verificar token
4
Handler
rota final
// middleware global
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`)
  next()  // OBRIGATÓRIO chamar next()
})

// middleware de rota específica
app.use('/api', authMiddleware)

// middleware built-in
app.use(express.json())                    // parsear JSON body
app.use(express.urlencoded({ extended: true }))  // form data
app.use(express.static('public'))         // arquivos estáticos

// ordem importa!
app.use(loggerMiddleware)    // 1º
app.use(authMiddleware)      // 2º
app.use('/api', router)      // 3º

Middleware de Autenticação

const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1]
  if (!token) return res.status(401).json({ error: 'Unauthorized' })
  try {
    const payload = verifyJWT(token)
    req.user = payload
    next()
  } catch {
    res.status(401).json({ error: 'Invalid token' })
  }
}

// aplicar em rotas específicas
router.get('/profile', authMiddleware, getProfile)
router.use(authMiddleware)  // todas as rotas do router

Error Handler

// DEVE ter 4 parâmetros (err, req, res, next)
app.use((err, req, res, next) => {
  console.error(err)
  const status = err.statusCode ?? 500
  res.status(status).json({
    error: err.message || 'Internal Server Error'
  })
})

// como usar: chamar next(err) em qualquer ponto
app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await db.findUser(req.params.id)
    if (!user) throw Object.assign(new Error('Not found'), { statusCode: 404 })
    res.json(user)
  } catch (err) {
    next(err)  // passa para o error handler
  }
})

Validação com Zod

import { z } from 'zod'

const createUserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).optional(),
  role: z.enum(['user', 'admin']).default('user')
})

// middleware de validação
const validate = (schema) => (req, res, next) => {
  const result = schema.safeParse(req.body)
  if (!result.success) {
    return res.status(400).json({ errors: result.error.flatten() })
  }
  req.validatedBody = result.data
  next()
}

router.post('/users', validate(createUserSchema), createUser)

JWT (Autenticação)

npm install jsonwebtoken
import jwt from 'jsonwebtoken'

const SECRET = process.env.JWT_SECRET

// criar token
const token = jwt.sign(
  { userId: user.id, role: user.role },
  SECRET,
  { expiresIn: '7d' }
)

// verificar
const payload = jwt.verify(token, SECRET)

// middleware
const authMiddleware = (req, res, next) => {
  const auth = req.headers.authorization
  if (!auth?.startsWith('Bearer ')) return res.status(401).json({ error: 'Unauthorized' })
  try {
    req.user = jwt.verify(auth.slice(7), SECRET)
    next()
  } catch (e) {
    res.status(401).json({ error: e.name === 'TokenExpiredError' ? 'Token expired' : 'Invalid token' })
  }
}

Upload de Arquivos (Multer)

import multer from 'multer'

// armazenar em disco
const storage = multer.diskStorage({
  destination: './uploads/',
  filename: (req, file, cb) => cb(null, Date.now() + '-' + file.originalname)
})
const upload = multer({
  storage,
  limits: { fileSize: 5 * 1024 * 1024 },  // 5MB
  fileFilter: (req, file, cb) => {
    if (file.mimetype.startsWith('image/')) cb(null, true)
    else cb(new Error('Somente imagens'))
  }
})

router.post('/upload', upload.single('file'), (req, res) => {
  res.json({ uploaded: req.file.originalname })
})

router.post('/upload-multi', upload.array('files', 10), handler)

Server-Sent Events (SSE)

app.get('/events', (req, res) => {
  res.set({
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  })
  res.flushHeaders()

  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`)
  }, 1000)

  req.on('close', () => clearInterval(interval))
})

WebSockets (ws)

npm install ws
import { WebSocketServer } from 'ws'
import http from 'http'

const server = http.createServer(app)
const wss = new WebSocketServer({ server })

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const msg = JSON.parse(data)
    wss.clients.forEach(client => {
      if (client.readyState === ws.OPEN) {
        client.send(JSON.stringify({ echo: msg }))
      }
    })
  })
  ws.send(JSON.stringify({ type: 'connected' }))
})

server.listen(3000)

Estrutura de Projeto

src/
├── index.js              # entry point, app.listen
├── app.js                # criar e configurar app (sem listen)
├── routes/
│   ├── index.js          # montar todos os routers
│   ├── users.js
│   └── posts.js
├── controllers/
│   ├── users.js          # lógica de cada handler
│   └── posts.js
├── middleware/
│   ├── auth.js
│   ├── validate.js
│   └── error.js
├── services/             # lógica de negócio
│   └── users.js
└── db/
    └── index.js          # conexão com banco
// app.js
import express from 'express'
import { usersRouter } from './routes/users.js'
import { errorHandler } from './middleware/error.js'

const app = express()
app.use(express.json())
app.use('/api/users', usersRouter)
app.use(errorHandler)
export default app

// index.js
import app from './app.js'
app.listen(3000, () => console.log('listening on 3000'))

Middlewares Populares

npm install cors helmet compression morgan cookie-parser multer rate-limit
import cors from 'cors'
import helmet from 'helmet'
import compression from 'compression'
import morgan from 'morgan'

// CORS
app.use(cors({
  origin: ['https://myapp.com', 'http://localhost:5173'],
  credentials: true
}))

// segurança HTTP headers
app.use(helmet())

// compressão gzip
app.use(compression())

// logging
app.use(morgan('dev'))

// rate limiting
import rateLimit from 'express-rate-limit'
app.use('/api/', rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: { error: 'Too many requests' }
}))

API REST — Exemplo Completo

// controllers/users.js
import { UserService } from '../services/users.js'

export const getUsers = async (req, res, next) => {
  try {
    const { page = 1, limit = 10 } = req.query
    const users = await UserService.findAll({ page: +page, limit: +limit })
    res.json(users)
  } catch (err) { next(err) }
}

export const getUserById = async (req, res, next) => {
  try {
    const user = await UserService.findById(req.params.id)
    if (!user) return res.status(404).json({ error: 'User not found' })
    res.json(user)
  } catch (err) { next(err) }
}

export const createUser = async (req, res, next) => {
  try {
    const user = await UserService.create(req.validatedBody)
    res.status(201).json(user)
  } catch (err) { next(err) }
}

export const deleteUser = async (req, res, next) => {
  try {
    await UserService.delete(req.params.id)
    res.status(204).send()
  } catch (err) { next(err) }
}

Testes

npm install -D jest supertest
import request from 'supertest'
import app from '../src/app.js'

describe('GET /api/users', () => {
  it('returns 200 and array', async () => {
    const res = await request(app).get('/api/users')
    expect(res.status).toBe(200)
    expect(Array.isArray(res.body)).toBe(true)
  })

  it('returns 401 without token', async () => {
    const res = await request(app).get('/api/users/me')
    expect(res.status).toBe(401)
  })

  it('creates user', async () => {
    const res = await request(app)
      .post('/api/users')
      .send({ name: 'Test', email: 'test@test.com' })
    expect(res.status).toBe(201)
    expect(res.body.id).toBeDefined()
  })
})

Variáveis de Ambiente

// src/config.js
export const config = {
  port: parseInt(process.env.PORT ?? '3000'),
  nodeEnv: process.env.NODE_ENV ?? 'development',
  jwtSecret: required('JWT_SECRET'),
  database: {
    url: required('DATABASE_URL')
  }
}

function required(key) {
  const val = process.env[key]
  if (!val) throw new Error(`Missing env var: ${key}`)
  return val
}