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
}