🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →

ElysiaJS: Servidor Base e Middlewares

Módulo 2⏱️ 25 minutos📚 Intermediário

🎯 Objetivo da Aula

Domine ElysiaJS: aprenda a criar servidor em Bun.js com middlewares essenciais (CORS, CSRF, Autenticação JWT). Guia prático para a fundação do seu projeto Restaurantix.

📋 O que vamos construir

  • Servidor ElysiaJS básico
  • Middleware de CORS
  • Middleware de CSRF
  • Middleware de Autenticação
  • Tratamento de erros global

🚀 Por que ElysiaJS?

ElysiaJS é um framework web ultra-rápido para Bun.js que oferece várias vantagens:

Performance Extrema

Construído especificamente para Bun.js, aproveitando todas as otimizações nativas. Até 10x mais rápido que Express.js em benchmarks.

Type Safety

TypeScript nativo com validação automática de schemas. Erros detectados em tempo de compilação, não runtime.

API Moderna

Sintaxe limpa e intuitiva. Middlewares como plugins reutilizáveis. Decoradores para injeção de dependências.

Ecossistema Rico

Plugins oficiais para JWT, CORS, Swagger, WebSocket. Integração nativa com Eden Treaty para client-side.

Como o ElysiaJS Funciona

ElysiaJS usa um sistema de plugins que são compostos de forma funcional:

elysia-plugins.ts
// Cada plugin é uma instância de Elysia
const authPlugin = new Elysia({ name: "auth" })
  .derive(() => ({ user: getCurrentUser() }))  // Injeta dependências
  .macro(() => ({ isSignedIn: true }))         // Define decoradores

// Plugins são compostos
const app = new Elysia()
  .use(corsPlugin)    // Middleware global
  .use(authPlugin)    // Plugin de autenticação
  .get("/", handler)  // Rota específica

🔄 Pipeline de Requisição: Request → Parse → Transform → Before Handle → Route Handler → After Handle → Response

🗄️ Por que Drizzle ORM?

Drizzle ORM é a escolha perfeita para TypeScript moderno:

Schema-First

Define seu schema uma vez e obtém tipos TypeScript automaticamente. Migrações geradas automaticamente.

Zero Runtime

Sem overhead de runtime. SQL puro gerado em tempo de build. Performance superior a ORMs tradicionais.

Type Safety

Queries completamente tipadas. Erros de SQL detectados em tempo de compilação. Auto-complete perfeito no editor.

SQL-Like API

API familiar para quem conhece SQL. Queries complexas simples de escrever. Suporte completo a relacionamentos.

Como o Drizzle Funciona

Drizzle usa um sistema de schema declarativo que gera tipos TypeScript:

schema.ts
// Schema definido uma vez
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 255 }).notNull(),
  email: varchar('email', { length: 255 }).unique(),
  createdAt: timestamp('created_at').defaultNow()
});

// Tipos gerados automaticamente
type User = typeof users.$inferSelect;      // { id: number, name: string, ... }
type NewUser = typeof users.$inferInsert;   // { name: string, email?: string, ... }

// Queries tipadas
const user = await db.select().from(users).where(eq(users.id, 1));

Integração Drizzle + ElysiaJS

Com drizzle-typebox, podemos usar schemas Drizzle diretamente na validação:

validation.ts
import { createInsertSchema } from 'drizzle-typebox';

// Schema Drizzle vira validação Elysia
const createUserSchema = createInsertSchema(users, {
  email: t.String({ format: 'email' })  // Refinamento customizado
});

new Elysia()
  .post('/users', ({ body }) => {
    // body já é tipado como NewUser
    return db.insert(users).values(body);
  }, {
    body: t.Omit(createUserSchema, 'id')  // Remove campos auto-gerados
  });

🎯 Type Safety Completa: Schema → Validação → Handler → Response
Uma mudança no banco reflete automaticamente em toda a aplicação!

🚀 Criando o Servidor Base

1. Estrutura do Projeto

Primeiro, vamos criar a estrutura de pastas do nosso projeto:

setup.sh
mkdir restaurantix
cd restaurantix
bun init -y

# Estrutura de pastas
mkdir -p src/http/routes
mkdir -p src/application/infra/db
touch src/http/server.ts
touch src/http/auth.ts
touch src/http/csrf.ts

2. Instalando Dependências

install-deps.sh
bun add elysia @elysiajs/cors @elysiajs/jwt @elysiajs/cookie
bun add drizzle-orm drizzle-kit postgres
bun add @paralleldrive/cuid2 dayjs resend zod
bun add -d @types/pg

3. Middleware de CSRF

Vamos criar o middleware de proteção CSRF:

src/http/csrf.ts
// src/http/csrf.ts
import { Elysia } from "elysia";
import { cookie } from "@elysiajs/cookie";
import { createId } from "@paralleldrive/cuid2";

export const csrf = new Elysia({ name: "csrf" })
  .use(cookie())
  .derive(({ cookie, set }) => {
    return {
      csrfToken: () => {
        if (!cookie.csrfToken) {
          const token = createId();
          cookie.csrfToken = {
            value: token,
            httpOnly: true,
            secure: process.env.NODE_ENV === "production",
            sameSite: "strict",
            maxAge: 60 * 60 * 24 * 7, // 7 days
          };
          return token;
        }
        return cookie.csrfToken.value;
      }
    };
  })
  .macro(({ onBeforeHandle }) => ({
    csrf: (enabled: boolean = true) => {
      if (!enabled) return;
      
      onBeforeHandle(({ cookie, headers, error }) => {
        const token = cookie.csrfToken?.value;
        const headerToken = headers["x-csrf-token"];
        
        if (!token || token !== headerToken) {
          return error(403, "Invalid CSRF token");
        }
      });
    }
  }));

4. Middleware de Autenticação

Agora o middleware de autenticação JWT:

src/http/auth.ts
// src/http/auth.ts
import { Elysia } from "elysia";
import { jwt } from "@elysiajs/jwt";
import { cookie } from "@elysiajs/cookie";

export const auth = new Elysia({ name: "auth" })
  .use(
    jwt({
      name: "jwt",
      secret: process.env.JWT_SECRET || "your-secret-key"
    })
  )
  .use(cookie())
  .derive(({ jwt, cookie }) => {
    return {
      getCurrentUser: async () => {
        const token = cookie.auth;
        if (!token) {
          throw new Error("Unauthorized");
        }
        
        const payload = await jwt.verify(token);
        if (!payload) {
          throw new Error("Unauthorized");
        }
        
        return payload as { sub: string };
      },
      signUser: async (userId: string) => {
        return await jwt.sign({ sub: userId });
      }
    };
  })
  .macro(({ onBeforeHandle }) => ({
    isSignedIn: (enabled: boolean = true) => {
      if (!enabled) return;
      
      onBeforeHandle(async ({ getCurrentUser, error }) => {
        try {
          await getCurrentUser();
        } catch {
          return error(401, "Unauthorized");
        }
      });
    }
  }));

5. Servidor Principal

Agora vamos criar o servidor principal com os middlewares:

src/http/server.ts
// src/http/server.ts
import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { auth } from "./auth";
import { csrf } from "./csrf";

const app = new Elysia()
  .use(cors())
  .use(csrf)
  .use(auth)
  .get("/", () => ({ message: "Restaurantix API" }))
  .onError(({ error, code, set }) => {
    console.error(error);
    switch (code) {
      case "VALIDATION":
        set.status = error.status;
        return error.toResponse();
      default:
        set.status = 500;
        console.error(error);
        return new Response(null, { status: 500 });
    }
  });

app.listen(3000, () => {
  console.log("Server is running on port 3000");
});

6. Testando o Servidor

test-server.sh
# Executar o servidor
bun run src/http/server.ts

# Testar em outro terminal
curl http://localhost:3000
# Resposta: {"message":"Restaurantix API"}

🎯 Próximos Passos

Na próxima aula, vamos começar a adicionar as rotas do sistema, começando pelas rotas de autenticação e CSRF token.

💡 Dica: Os middlewares são executados na ordem que são registrados. CORS primeiro, depois CSRF, depois Auth.

✅ Checkpoint: Você agora tem um servidor ElysiaJS funcionando com middlewares de segurança configurados!