AzuraJS Logo
AzuraJSFramework
v2.2 Beta

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

Próximos Passos 📖

On this page