Exemplos
Exemplos práticos e completos de aplicações AzuraJS
Exemplos 📚
Aprenda com exemplos práticos e completos de aplicações reais construídas com AzuraJS.
API REST Completa 🎯
Estrutura do Projeto
src/
controllers/
UserController.ts
PostController.ts
models/
User.ts
Post.ts
middleware/
auth.ts
errorHandler.ts
config/
database.ts
server.tsUser Model
// models/User.ts
export interface User {
id: string;
name: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
}
export interface CreateUserDto {
name: string;
email: string;
password: string;
}
export interface UpdateUserDto {
name?: string;
email?: string;
}User Controller
TypeScript
// controllers/UserController.ts
import { Controller, Get, Post, Put, Delete, Body, Param, Res } from "azurajs/decorators";
import { HttpError } from "azurajs/http-error";
import { hashPassword, comparePassword } from "../utils/crypto";
@Controller("/api/users")
export class UserController {
@Get()
async getAllUsers() {
const users = await db.query("SELECT id, name, email, created_at FROM users");
return { users };
}
@Get("/:id")
async getUser(@Param("id") id: string) {
const user = await db.query(
"SELECT id, name, email, created_at FROM users WHERE id = $1",
[id]
);
if (!user) {
throw new HttpError(404, "Usuário não encontrado");
}
return { user };
}
@Post()
async createUser(@Body() data: CreateUserDto, @Res() res: ResponseServer) {
// Validar dados
if (!data.email || !data.password) {
throw new HttpError(400, "Email e senha são obrigatórios");
}
// Verificar se email já existe
const exists = await db.query("SELECT id FROM users WHERE email = $1", [data.email]);
if (exists) {
throw new HttpError(409, "Email já está em uso");
}
// Hash da senha
const hashedPassword = await hashPassword(data.password);
// Criar usuário
const user = await db.query(
"INSERT INTO users (name, email, password) VALUES ($1, $2, $3) RETURNING id, name, email, created_at",
[data.name, data.email, hashedPassword]
);
res.status(201).json({ user });
}
@Put("/:id")
async updateUser(@Param("id") id: string, @Body() data: UpdateUserDto) {
const user = await db.query(
"UPDATE users SET name = COALESCE($1, name), email = COALESCE($2, email), updated_at = NOW() WHERE id = $3 RETURNING id, name, email",
[data.name, data.email, id]
);
if (!user) {
throw new HttpError(404, "Usuário não encontrado");
}
return { user };
}
@Delete("/:id")
async deleteUser(@Param("id") id: string, @Res() res: ResponseServer) {
const result = await db.query("DELETE FROM users WHERE id = $1", [id]);
if (result.rowCount === 0) {
throw new HttpError(404, "Usuário não encontrado");
}
res.status(204).end();
}
}JavaScript
// controllers/UserController.js
const { HttpError } = require("azurajs/http-error");
const { hashPassword } = require("../utils/crypto");
class UserController {
async getAllUsers(req, res) {
const users = await db.query("SELECT id, name, email, created_at FROM users");
res.json({ users });
}
async getUser(req, res) {
const { id } = req.params;
const user = await db.query(
"SELECT id, name, email, created_at FROM users WHERE id = $1",
[id]
);
if (!user) {
throw new HttpError(404, "Usuário não encontrado");
}
res.json({ user });
}
async createUser(req, res) {
const data = req.body;
// Validar dados
if (!data.email || !data.password) {
throw new HttpError(400, "Email e senha são obrigatórios");
}
// Verificar se email já existe
const exists = await db.query("SELECT id FROM users WHERE email = $1", [data.email]);
if (exists) {
throw new HttpError(409, "Email já está em uso");
}
// Hash da senha
const hashedPassword = await hashPassword(data.password);
// Criar usuário
const user = await db.query(
"INSERT INTO users (name, email, password) VALUES ($1, $2, $3) RETURNING id, name, email, created_at",
[data.name, data.email, hashedPassword]
);
res.status(201).json({ user });
}
async updateUser(req, res) {
const { id } = req.params;
const data = req.body;
const user = await db.query(
"UPDATE users SET name = COALESCE($1, name), email = COALESCE($2, email), updated_at = NOW() WHERE id = $3 RETURNING id, name, email",
[data.name, data.email, id]
);
if (!user) {
throw new HttpError(404, "Usuário não encontrado");
}
res.json({ user });
}
async deleteUser(req, res) {
const { id } = req.params;
const result = await db.query("DELETE FROM users WHERE id = $1", [id]);
if (result.rowCount === 0) {
throw new HttpError(404, "Usuário não encontrado");
}
res.status(204).end();
}
}
// Registrar rotas
const controller = new UserController();
app.get("/api/users", controller.getAllUsers);
app.get("/api/users/:id", controller.getUser);
app.post("/api/users", controller.createUser);
app.put("/api/users/:id", controller.updateUser);
app.delete("/api/users/:id", controller.deleteUser);
module.exports = { UserController };Autenticação com JWT 🔐
Auth Controller
// controllers/AuthController.ts
import { Controller, Post, Get, Body, Headers, Res } from "azurajs/decorators";
import { HttpError } from "azurajs/http-error";
import type { ResponseServer } from "azurajs";
import { sign, verify } from "jsonwebtoken";
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
@Controller("/auth")
export class AuthController {
@Post("/register")
async register(@Body() data: CreateUserDto, @Res() res: ResponseServer) {
// Validar dados
if (!data.email || !data.password) {
throw new HttpError(400, "Email e senha são obrigatórios");
}
// Verificar se usuário existe
const exists = await db.query("SELECT id FROM users WHERE email = $1", [data.email]);
if (exists) {
throw new HttpError(409, "Email já está em uso");
}
// Hash da senha
const hashedPassword = await hashPassword(data.password);
// Criar usuário
const user = await db.query(
"INSERT INTO users (name, email, password) VALUES ($1, $2, $3) RETURNING id, name, email",
[data.name, data.email, hashedPassword]
);
// Gerar JWT
const token = sign(
{ userId: user.id, email: user.email },
JWT_SECRET,
{ expiresIn: "7d" }
);
res.status(201).json({ user, token });
}
@Post("/login")
async login(@Body() credentials: { email: string; password: string }) {
// Buscar usuário
const user = await db.query(
"SELECT id, name, email, password FROM users WHERE email = $1",
[credentials.email]
);
if (!user) {
throw new HttpError(401, "Credenciais inválidas");
}
// Verificar senha
const valid = await comparePassword(credentials.password, user.password);
if (!valid) {
throw new HttpError(401, "Credenciais inválidas");
}
// Gerar JWT
const token = sign(
{ userId: user.id, email: user.email },
JWT_SECRET,
{ expiresIn: "7d" }
);
return {
user: { id: user.id, name: user.name, email: user.email },
token
};
}
@Get("/me")
async getCurrentUser(@Headers("authorization") auth: string) {
if (!auth) {
throw new HttpError(401, "Token não fornecido");
}
const token = auth.replace("Bearer ", "");
try {
const decoded = verify(token, JWT_SECRET) as any;
const user = await db.query(
"SELECT id, name, email FROM users WHERE id = $1",
[decoded.userId]
);
if (!user) {
throw new HttpError(404, "Usuário não encontrado");
}
return { user };
} catch (error) {
throw new HttpError(401, "Token inválido");
}
}
}Auth Middleware
// middleware/auth.ts
import { verify } from "jsonwebtoken";
import type { RequestServer, ResponseServer } from "azurajs";
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
export function authMiddleware(
req: RequestServer,
res: ResponseServer,
next: () => void
) {
const auth = req.headers.authorization;
if (!auth) {
return res.status(401).json({ error: "Token não fornecido" });
}
const token = auth.replace("Bearer ", "");
try {
const decoded = verify(token, JWT_SECRET) as any;
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: "Token inválido" });
}
}Upload de Arquivos 📤
import { Controller, Post, Req, Res } from "azurajs/decorators";
import { HttpError } from "azurajs/http-error";
import type { RequestServer, ResponseServer } from "azurajs";
import { writeFile } from "fs/promises";
import { randomUUID } from "crypto";
@Controller("/api/uploads")
export class UploadController {
@Post("/image")
async uploadImage(@Req() req: RequestServer, @Res() res: ResponseServer) {
// Verificar Content-Type
const contentType = req.headers["content-type"];
if (!contentType?.includes("multipart/form-data")) {
throw new HttpError(400, "Content-Type deve ser multipart/form-data");
}
// Parsear multipart data (exemplo simplificado)
const boundary = contentType.split("boundary=")[1];
const parts = parseMultipart(req.body, boundary);
const file = parts.find(p => p.name === "file");
if (!file) {
throw new HttpError(400, "Arquivo não encontrado");
}
// Validar tipo de arquivo
if (!file.contentType.startsWith("image/")) {
throw new HttpError(400, "Apenas imagens são permitidas");
}
// Validar tamanho (máximo 5MB)
if (file.data.length > 5 * 1024 * 1024) {
throw new HttpError(400, "Arquivo muito grande (máximo 5MB)");
}
// Salvar arquivo
const filename = `${randomUUID()}.${file.extension}`;
const path = `./uploads/${filename}`;
await writeFile(path, file.data);
// Salvar no banco de dados
const upload = await db.query(
"INSERT INTO uploads (filename, original_name, size, mime_type, user_id) VALUES ($1, $2, $3, $4, $5) RETURNING *",
[filename, file.originalName, file.data.length, file.contentType, req.user.userId]
);
res.status(201).json({
upload: {
id: upload.id,
url: `/uploads/${filename}`,
originalName: file.originalName,
size: file.data.length
}
});
}
@Get("/:filename")
async getFile(@Param("filename") filename: string, @Res() res: ResponseServer) {
const upload = await db.query(
"SELECT * FROM uploads WHERE filename = $1",
[filename]
);
if (!upload) {
throw new HttpError(404, "Arquivo não encontrado");
}
const file = await readFile(`./uploads/${filename}`);
res.setHeader("Content-Type", upload.mime_type);
res.setHeader("Content-Length", file.length.toString());
res.send(file);
}
}WebSocket com AzuraJS 🔌
import { AzuraClient } from "azurajs";
import { WebSocketServer } from "ws";
const app = new AzuraClient();
const server = await app.listen(3000);
// Criar WebSocket server
const wss = new WebSocketServer({ server });
interface Client {
id: string;
ws: any;
userId?: string;
}
const clients = new Map<string, Client>();
wss.on("connection", (ws, req) => {
const clientId = randomUUID();
clients.set(clientId, { id: clientId, ws });
console.log(`Cliente conectado: ${clientId}`);
ws.on("message", (data: Buffer) => {
try {
const message = JSON.parse(data.toString());
// Autenticar
if (message.type === "auth") {
const token = message.token;
const decoded = verify(token, JWT_SECRET) as any;
const client = clients.get(clientId);
if (client) {
client.userId = decoded.userId;
ws.send(JSON.stringify({ type: "auth", success: true }));
}
}
// Enviar mensagem
else if (message.type === "message") {
const client = clients.get(clientId);
if (!client?.userId) {
return ws.send(JSON.stringify({
type: "error",
message: "Não autenticado"
}));
}
// Broadcast para todos os clientes
const payload = {
type: "message",
from: client.userId,
text: message.text,
timestamp: new Date().toISOString()
};
clients.forEach(c => {
if (c.userId) {
c.ws.send(JSON.stringify(payload));
}
});
}
} catch (error) {
console.error("WebSocket error:", error);
}
});
ws.on("close", () => {
clients.delete(clientId);
console.log(`Cliente desconectado: ${clientId}`);
});
});Integração com Banco de Dados 💾
PostgreSQL com Prisma
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(uuid())
name String
email String @unique
password String
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(uuid())
title String
content String
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}// controllers/PostController.ts
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
@Controller("/api/posts")
export class PostController {
@Get()
async getPosts(@Query("published") published: string) {
const posts = await prisma.post.findMany({
where: published ? { published: published === "true" } : undefined,
include: {
author: {
select: {
id: true,
name: true,
email: true
}
}
},
orderBy: {
createdAt: "desc"
}
});
return { posts };
}
@Post()
async createPost(@Body() data: any, @Req() req: RequestServer) {
const post = await prisma.post.create({
data: {
title: data.title,
content: data.content,
published: data.published || false,
authorId: req.user.userId
},
include: {
author: {
select: {
id: true,
name: true,
email: true
}
}
}
});
return { post };
}
@Put("/:id")
async updatePost(@Param("id") id: string, @Body() data: any) {
const post = await prisma.post.update({
where: { id },
data: {
title: data.title,
content: data.content,
published: data.published
}
});
return { post };
}
@Delete("/:id")
async deletePost(@Param("id") id: string, @Res() res: ResponseServer) {
await prisma.post.delete({
where: { id }
});
res.status(204).end();
}
}Servidor Completo 🚀
// server.ts
import { AzuraClient, applyDecorators } from "azurajs";
import cluster from "cluster";
import { cpus } from "os";
// Controllers
import { UserController } from "./controllers/UserController";
import { AuthController } from "./controllers/AuthController";
import { PostController } from "./controllers/PostController";
import { UploadController } from "./controllers/UploadController";
// Middleware
import { authMiddleware } from "./middleware/auth";
import { errorHandler } from "./middleware/errorHandler";
import { LoggingMiddleware } from "azurajs/middleware";
const PORT = process.env.PORT || 3000;
const numCPUs = cpus().length;
if (cluster.isPrimary && process.env.NODE_ENV === "production") {
console.log(`🚀 Starting ${numCPUs} workers...`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on("exit", (worker) => {
console.log(`Worker ${worker.process.pid} died, starting new worker...`);
cluster.fork();
});
} else {
const app = new AzuraClient({
environment: process.env.NODE_ENV as any || "development",
server: {
port: PORT
},
logging: {
level: "info"
},
plugins: {
cors: {
enabled: true,
origin: process.env.ALLOWED_ORIGINS?.split(",") || ["http://localhost:3000"],
credentials: true
},
rateLimit: {
enabled: true,
windowMs: 60000,
max: 100
}
}
});
// Middleware global
app.use(LoggingMiddleware);
// Rotas públicas
applyDecorators(app, [AuthController]);
// Rotas protegidas
app.use("/api", authMiddleware);
applyDecorators(app, [UserController, PostController, UploadController]);
// Error handler (último!)
app.use(errorHandler);
await app.listen(PORT);
console.log(`✅ Worker ${process.pid} listening on port ${PORT}`);
}Testando a API 🧪
// test/api.test.ts
import { describe, it, expect } from "vitest";
const BASE_URL = "http://localhost:3000";
describe("Auth API", () => {
let token: string;
it("should register a new user", async () => {
const response = await fetch(`${BASE_URL}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Test User",
email: "[email protected]",
password: "password123"
})
});
const data = await response.json();
expect(response.status).toBe(201);
expect(data.user).toBeDefined();
expect(data.token).toBeDefined();
token = data.token;
});
it("should login", async () => {
const response = await fetch(`${BASE_URL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: "[email protected]",
password: "password123"
})
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data.token).toBeDefined();
});
it("should get current user", async () => {
const response = await fetch(`${BASE_URL}/auth/me`, {
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data.user.email).toBe("[email protected]");
});
});Melhores Práticas ✨
Organize seu código: Use controllers, services, repositories
Valide entrada: Sempre valide dados do cliente
Use TypeScript: Aproveite type safety
Implemente autenticação: Proteja rotas sensíveis
Trate erros: Use error handlers globais
Log tudo: Facilita debugging
Teste sua API: Escreva testes automatizados
