Error Handling
Trate erros de forma elegante no AzuraJS
Error Handling 🚨
AzuraJS fornece ferramentas poderosas para tratar erros de forma consistente e elegante.
Classe HttpError 💥
Use HttpError para lançar erros HTTP com códigos de status:
TypeScript
import { Get, Param } from "azurajs/decorators";
import { HttpError } from "azurajs/http-error";
@Get("/:id")
getUser(@Param("id") id: string) {
const user = findUserById(id);
if (!user) {
throw new HttpError(404, "Usuário não encontrado");
}
return { user };
}JavaScript
const { HttpError } = require("azurajs/http-error");
app.get("/:id", (req, res) => {
const { id } = req.params;
const user = findUserById(id);
if (!user) {
throw new HttpError(404, "Usuário não encontrado");
}
res.json({ user });
});Códigos de Status Comuns
// 400 Bad Request
throw new HttpError(400, "Dados inválidos");
// 401 Unauthorized
throw new HttpError(401, "Não autorizado");
// 403 Forbidden
throw new HttpError(403, "Acesso negado");
// 404 Not Found
throw new HttpError(404, "Recurso não encontrado");
// 409 Conflict
throw new HttpError(409, "Usuário já existe");
// 422 Unprocessable Entity
throw new HttpError(422, "Validação falhou");
// 500 Internal Server Error
throw new HttpError(500, "Erro interno do servidor");Middleware de Tratamento de Erros ⚙️
Crie um middleware global para capturar todos os erros:
function errorHandler(
error: any,
req: RequestServer,
res: ResponseServer,
next: () => void
) {
console.error("Error:", error);
// HttpError
if (error instanceof HttpError) {
return res.status(error.statusCode).json({
error: error.message,
statusCode: error.statusCode
});
}
// Erro de validação (Zod)
if (error.name === "ZodError") {
return res.status(400).json({
error: "Validação falhou",
details: error.errors
});
}
// Erro genérico
res.status(500).json({
error: "Erro interno do servidor",
message: process.env.NODE_ENV === "development" ? error.message : undefined
});
}
// Registrar por último
app.use(errorHandler);Try-Catch em Async Handlers 🔄
Sempre envolva código assíncrono em try-catch:
@Get("/posts")
async getPosts(@Res() res: ResponseServer) {
try {
const posts = await fetchPostsFromDB();
res.json({ posts });
} catch (error) {
console.error("Error fetching posts:", error);
throw new HttpError(500, "Erro ao buscar posts");
}
}Wrapper de Async Handler 🎁
Crie um wrapper para evitar repetição de try-catch:
function asyncHandler(fn: Function) {
return async (req: RequestServer, res: ResponseServer, next: () => void) => {
try {
await fn(req, res, next);
} catch (error) {
next(error);
}
};
}
// Usar como middleware
app.get("/users", asyncHandler(async (req, res) => {
const users = await getUsersFromDB();
res.json({ users });
}));Classes de Erro Customizadas 🎨
Crie classes de erro específicas para seu domínio:
class ValidationError extends HttpError {
constructor(message: string, public fields?: Record<string, string>) {
super(422, message);
this.name = "ValidationError";
}
}
class AuthenticationError extends HttpError {
constructor(message = "Não autenticado") {
super(401, message);
this.name = "AuthenticationError";
}
}
class AuthorizationError extends HttpError {
constructor(message = "Acesso negado") {
super(403, message);
this.name = "AuthorizationError";
}
}
class NotFoundError extends HttpError {
constructor(resource: string) {
super(404, `${resource} não encontrado`);
this.name = "NotFoundError";
}
}
class ConflictError extends HttpError {
constructor(message: string) {
super(409, message);
this.name = "ConflictError";
}
}
// Usar nos controllers
@Get("/:id")
getUser(@Param("id") id: string) {
const user = findUserById(id);
if (!user) {
throw new NotFoundError("Usuário");
}
return { user };
}
@Post()
createUser(@Body() data: any) {
const exists = userExists(data.email);
if (exists) {
throw new ConflictError("Email já está em uso");
}
return createUser(data);
}Tratamento de Erros de Validação ✅
Integre com Zod para validação type-safe:
import { z } from "zod";
const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive()
});
@Post("/users")
createUser(@Body() data: unknown, @Res() res: ResponseServer) {
try {
const validData = CreateUserSchema.parse(data);
const user = saveUser(validData);
res.status(201).json({ user });
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: "Validação falhou",
details: error.errors.map(e => ({
field: e.path.join("."),
message: e.message
}))
});
}
throw error;
}
}Logging de Erros 📝
Log erros para análise posterior:
import { Logger } from "azurajs";
function errorHandler(
error: any,
req: RequestServer,
res: ResponseServer,
next: () => void
) {
// Log detalhes do erro
Logger.error("Request error", {
error: error.message,
stack: error.stack,
method: req.method,
url: req.url,
ip: req.ip,
userAgent: req.headers["user-agent"],
timestamp: new Date().toISOString()
});
// Responder cliente
if (error instanceof HttpError) {
return res.status(error.statusCode).json({
error: error.message
});
}
res.status(500).json({
error: "Erro interno do servidor"
});
}
app.use(errorHandler);Rastreamento de Erros 🔍
Integre com serviços de rastreamento como Sentry:
import * as Sentry from "@sentry/node";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0
});
function errorHandler(
error: any,
req: RequestServer,
res: ResponseServer,
next: () => void
) {
// Enviar para Sentry
Sentry.captureException(error, {
extra: {
method: req.method,
url: req.url,
ip: req.ip
}
});
// Responder cliente
if (error instanceof HttpError) {
return res.status(error.statusCode).json({
error: error.message
});
}
res.status(500).json({
error: "Erro interno do servidor"
});
}
app.use(errorHandler);Erros em Middleware 🔌
Passe erros para o próximo middleware usando next():
function authMiddleware(
req: RequestServer,
res: ResponseServer,
next: () => void
) {
try {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
throw new AuthenticationError("Token não fornecido");
}
const decoded = verifyJWT(token);
req.user = decoded;
next();
} catch (error) {
next(error); // Passar erro para error handler
}
}
app.use(authMiddleware);
app.use(errorHandler);Erros de Banco de Dados 💾
Trate erros específicos de banco de dados:
class DatabaseError extends HttpError {
constructor(message: string, public originalError?: any) {
super(500, message);
this.name = "DatabaseError";
}
}
@Get("/users")
async getUsers(@Res() res: ResponseServer) {
try {
const users = await db.query("SELECT * FROM users");
res.json({ users });
} catch (error: any) {
// Erro de conexão
if (error.code === "ECONNREFUSED") {
throw new DatabaseError("Banco de dados indisponível", error);
}
// Erro de sintaxe SQL
if (error.code === "42P01") {
throw new DatabaseError("Tabela não existe", error);
}
// Erro genérico
throw new DatabaseError("Erro ao consultar banco de dados", error);
}
}Formato Consistente de Resposta de Erro 📋
Use um formato padrão para todas as respostas de erro:
interface ErrorResponse {
error: {
message: string;
code: string;
statusCode: number;
details?: any;
timestamp: string;
path: string;
};
}
function errorHandler(
error: any,
req: RequestServer,
res: ResponseServer,
next: () => void
) {
const statusCode = error instanceof HttpError ? error.statusCode : 500;
const response: ErrorResponse = {
error: {
message: error.message || "Erro interno do servidor",
code: error.name || "INTERNAL_ERROR",
statusCode,
timestamp: new Date().toISOString(),
path: req.url
}
};
// Adicionar detalhes em desenvolvimento
if (process.env.NODE_ENV === "development") {
response.error.details = {
stack: error.stack,
...error
};
}
res.status(statusCode).json(response);
}Tratamento de Erros de Rede 🌐
Trate erros ao fazer requisições externas:
@Get("/external-data")
async getExternalData(@Res() res: ResponseServer) {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new HttpError(
502,
`API externa retornou ${response.status}`
);
}
const data = await response.json();
res.json({ data });
} catch (error: any) {
if (error.code === "ENOTFOUND") {
throw new HttpError(503, "Serviço externo indisponível");
}
if (error.code === "ETIMEDOUT") {
throw new HttpError(504, "Timeout ao conectar ao serviço externo");
}
throw error;
}
}Circuit Breaker Pattern 🔌
Implemente circuit breaker para falhas em cascata:
class CircuitBreaker {
private failures = 0;
private lastFailTime = 0;
private state: "CLOSED" | "OPEN" | "HALF_OPEN" = "CLOSED";
constructor(
private threshold = 5,
private timeout = 60000
) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === "OPEN") {
if (Date.now() - this.lastFailTime > this.timeout) {
this.state = "HALF_OPEN";
} else {
throw new HttpError(503, "Serviço temporariamente indisponível");
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess() {
this.failures = 0;
this.state = "CLOSED";
}
private onFailure() {
this.failures++;
this.lastFailTime = Date.now();
if (this.failures >= this.threshold) {
this.state = "OPEN";
}
}
}
const dbCircuitBreaker = new CircuitBreaker(5, 60000);
@Get("/users")
async getUsers(@Res() res: ResponseServer) {
try {
const users = await dbCircuitBreaker.execute(async () => {
return await db.query("SELECT * FROM users");
});
res.json({ users });
} catch (error) {
if (error instanceof HttpError && error.statusCode === 503) {
return res.status(503).json({
error: "Serviço temporariamente indisponível. Tente novamente em alguns minutos."
});
}
throw error;
}
}Exemplo Completo 🎯
// errors/CustomErrors.ts
export class ValidationError extends HttpError {
constructor(message: string, public fields?: Record<string, string>) {
super(422, message);
this.name = "ValidationError";
}
}
export class NotFoundError extends HttpError {
constructor(resource: string) {
super(404, `${resource} não encontrado`);
this.name = "NotFoundError";
}
}
export class ConflictError extends HttpError {
constructor(message: string) {
super(409, message);
this.name = "ConflictError";
}
}
// middleware/errorHandler.ts
import { Logger } from "azurajs";
import * as Sentry from "@sentry/node";
export function errorHandler(
error: any,
req: RequestServer,
res: ResponseServer,
next: () => void
) {
// Log erro
Logger.error("Request error", {
error: error.message,
stack: error.stack,
method: req.method,
url: req.url,
ip: req.ip
});
// Enviar para Sentry em produção
if (process.env.NODE_ENV === "production") {
Sentry.captureException(error);
}
// Responder cliente
const statusCode = error instanceof HttpError ? error.statusCode : 500;
const response = {
error: {
message: error.message,
code: error.name,
statusCode,
timestamp: new Date().toISOString()
}
};
// Detalhes em desenvolvimento
if (process.env.NODE_ENV === "development") {
response.error.details = error.stack;
}
res.status(statusCode).json(response);
}
// controllers/UserController.ts
import { NotFoundError, ConflictError } from "../errors/CustomErrors";
@Controller("/api/users")
export class UserController {
@Get("/:id")
async getUser(@Param("id") id: string) {
const user = await findUserById(id);
if (!user) {
throw new NotFoundError("Usuário");
}
return { user };
}
@Post()
async createUser(@Body() data: CreateUserDto) {
const exists = await userExists(data.email);
if (exists) {
throw new ConflictError("Email já está em uso");
}
try {
const user = await saveUser(data);
return { user };
} catch (error) {
throw new HttpError(500, "Erro ao criar usuário");
}
}
}
// server.ts
import { AzuraClient, applyDecorators } from "azurajs";
import { errorHandler } from "./middleware/errorHandler";
import { UserController } from "./controllers/UserController";
const app = new AzuraClient();
// Registrar controllers
applyDecorators(app, [UserController]);
// Error handler (por último!)
app.use(errorHandler);
await app.listen(3000);Melhores Práticas ✨
Sempre use try-catch em código assíncrono: Evite crashes não tratados
Crie classes de erro customizadas: Facilita identificação e tratamento
Log todos os erros: Essencial para debugging e monitoramento
Nunca exponha stack traces em produção: Pode vazar informações sensíveis
Registre error handler por último: Deve capturar erros de todos os middlewares anteriores
