Voltar para o Blog

Deno 2.1 Revoluciona o Desenvolvimento JavaScript

Olá HaWkers, o Deno acaba de lançar a versão 2.1, e esta atualização traz mudanças que podem finalmente torná-lo uma alternativa viável ao Node.js para projetos em produção. Com compatibilidade total com o ecossistema npm, sistema de permissões aprimorado e performance impressionante, o Deno está mais maduro do que nunca.

Você já considerou migrar seus projetos para Deno? Vamos explorar o que a versão 2.1 oferece e se faz sentido para seu próximo projeto.

O Que Há de Novo no Deno 2.1

Esta versão representa o maior salto de maturidade do Deno desde seu lançamento. Ryan Dahl, criador tanto do Node.js quanto do Deno, finalmente entregou a compatibilidade que a comunidade pedia.

Principais Novidades

Compatibilidade Node.js:

  • 100% dos pacotes npm funcionam nativamente
  • Suporte a package.json e node_modules
  • APIs do Node.js implementadas (fs, http, crypto, etc.)
  • Workspaces e monorepos suportados

Performance:

  • Cold start 40% mais rápido
  • Consumo de memória 30% menor
  • HTTP/3 nativo com QUIC
  • Worker threads otimizados

Developer Experience:

  • LSP melhorado para IDEs
  • Debugging integrado com Chrome DevTools
  • Coverage de código nativo
  • Formatador e linter built-in atualizados

🚀 Destaque: O Deno agora consegue executar projetos Next.js, Remix e Astro sem modificações.

Configuração e Primeiros Passos

Vamos começar com a instalação e configuração básica do Deno 2.1:

Instalação

# macOS/Linux
curl -fsSL https://deno.land/install.sh | sh

# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex

# Verificar instalação
deno --version
# deno 2.1.0
# v8 12.9.0
# typescript 5.6.0

Configuração do Projeto

Crie um arquivo deno.json para configurar seu projeto:

{
  "name": "@myorg/my-project",
  "version": "1.0.0",
  "tasks": {
    "dev": "deno run --watch --allow-net --allow-read src/main.ts",
    "build": "deno compile --output=dist/app src/main.ts",
    "test": "deno test --coverage=coverage/",
    "lint": "deno lint",
    "fmt": "deno fmt"
  },
  "imports": {
    "@std/": "https://deno.land/std@0.220.0/",
    "hono": "npm:hono@4.0.0",
    "drizzle-orm": "npm:drizzle-orm@0.30.0"
  },
  "compilerOptions": {
    "strict": true,
    "jsx": "react-jsx",
    "jsxImportSource": "npm:react"
  },
  "nodeModulesDir": true,
  "lock": true
}

Criando uma API REST Moderna

Vamos criar uma API REST completa usando Deno 2.1 com Hono e Drizzle ORM:

Estrutura do Projeto

my-api/
├── deno.json
├── src/
│   ├── main.ts
│   ├── routes/
│   │   ├── users.ts
│   │   └── posts.ts
│   ├── db/
│   │   ├── schema.ts
│   │   └── client.ts
│   ├── middleware/
│   │   ├── auth.ts
│   │   └── logger.ts
│   └── utils/
│       └── validation.ts
└── tests/
    └── users.test.ts

Configuração do Servidor

// src/main.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { secureHeaders } from "hono/secure-headers";
import { rateLimiter } from "hono/rate-limiter";

import { usersRouter } from "./routes/users.ts";
import { postsRouter } from "./routes/posts.ts";
import { authMiddleware } from "./middleware/auth.ts";
import { errorHandler } from "./middleware/error.ts";

const app = new Hono();

// Middleware global
app.use("*", logger());
app.use("*", cors({
  origin: ["https://myapp.com", "http://localhost:3000"],
  credentials: true,
}));
app.use("*", secureHeaders());

// Rate limiting
app.use("*", rateLimiter({
  windowMs: 15 * 60 * 1000, // 15 minutos
  limit: 100, // 100 requisições por janela
  keyGenerator: (c) => c.req.header("x-forwarded-for") || "anonymous",
}));

// Health check
app.get("/health", (c) => {
  return c.json({
    status: "healthy",
    timestamp: new Date().toISOString(),
    version: Deno.env.get("APP_VERSION") || "1.0.0",
  });
});

// Rotas públicas
app.route("/api/v1/users", usersRouter);

// Rotas protegidas
app.use("/api/v1/posts/*", authMiddleware);
app.route("/api/v1/posts", postsRouter);

// Error handler global
app.onError(errorHandler);

// 404 handler
app.notFound((c) => {
  return c.json({ error: "Not Found", path: c.req.path }, 404);
});

// Iniciar servidor
const port = parseInt(Deno.env.get("PORT") || "8000");

console.log(`🦕 Server running on http://localhost:${port}`);

Deno.serve({ port }, app.fetch);

Schema do Banco de Dados

// src/db/schema.ts
import { pgTable, serial, text, timestamp, varchar, boolean, integer } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";

export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  name: varchar("name", { length: 100 }).notNull(),
  passwordHash: text("password_hash").notNull(),
  avatarUrl: text("avatar_url"),
  isActive: boolean("is_active").default(true),
  createdAt: timestamp("created_at").defaultNow(),
  updatedAt: timestamp("updated_at").defaultNow(),
});

export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 255 }).notNull(),
  slug: varchar("slug", { length: 255 }).notNull().unique(),
  content: text("content").notNull(),
  excerpt: varchar("excerpt", { length: 500 }),
  authorId: integer("author_id").references(() => users.id),
  published: boolean("published").default(false),
  viewCount: integer("view_count").default(0),
  createdAt: timestamp("created_at").defaultNow(),
  updatedAt: timestamp("updated_at").defaultNow(),
});

// Relações
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}));

// Types inferidos
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;

Rotas de Usuários

// src/routes/users.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
import { eq } from "drizzle-orm";
import { db } from "../db/client.ts";
import { users, type NewUser } from "../db/schema.ts";
import { hashPassword, comparePassword } from "../utils/crypto.ts";
import { generateToken } from "../utils/jwt.ts";

const usersRouter = new Hono();

// Schemas de validação
const createUserSchema = z.object({
  email: z.string().email("Email inválido"),
  name: z.string().min(2, "Nome muito curto").max(100),
  password: z.string().min(8, "Senha deve ter no mínimo 8 caracteres"),
});

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string(),
});

// POST /users - Criar usuário
usersRouter.post(
  "/",
  zValidator("json", createUserSchema),
  async (c) => {
    const data = c.req.valid("json");

    // Verificar se email já existe
    const existing = await db
      .select()
      .from(users)
      .where(eq(users.email, data.email))
      .limit(1);

    if (existing.length > 0) {
      return c.json({ error: "Email já cadastrado" }, 409);
    }

    // Hash da senha
    const passwordHash = await hashPassword(data.password);

    // Criar usuário
    const [newUser] = await db
      .insert(users)
      .values({
        email: data.email,
        name: data.name,
        passwordHash,
      })
      .returning({
        id: users.id,
        email: users.email,
        name: users.name,
        createdAt: users.createdAt,
      });

    return c.json(newUser, 201);
  }
);

// POST /users/login - Autenticação
usersRouter.post(
  "/login",
  zValidator("json", loginSchema),
  async (c) => {
    const { email, password } = c.req.valid("json");

    // Buscar usuário
    const [user] = await db
      .select()
      .from(users)
      .where(eq(users.email, email))
      .limit(1);

    if (!user) {
      return c.json({ error: "Credenciais inválidas" }, 401);
    }

    // Verificar senha
    const validPassword = await comparePassword(password, user.passwordHash);

    if (!validPassword) {
      return c.json({ error: "Credenciais inválidas" }, 401);
    }

    // Gerar token JWT
    const token = await generateToken({
      userId: user.id,
      email: user.email,
    });

    return c.json({
      token,
      user: {
        id: user.id,
        email: user.email,
        name: user.name,
      },
    });
  }
);

// GET /users/:id - Buscar usuário por ID
usersRouter.get("/:id", async (c) => {
  const id = parseInt(c.req.param("id"));

  if (isNaN(id)) {
    return c.json({ error: "ID inválido" }, 400);
  }

  const [user] = await db
    .select({
      id: users.id,
      email: users.email,
      name: users.name,
      avatarUrl: users.avatarUrl,
      createdAt: users.createdAt,
    })
    .from(users)
    .where(eq(users.id, id))
    .limit(1);

  if (!user) {
    return c.json({ error: "Usuário não encontrado" }, 404);
  }

  return c.json(user);
});

export { usersRouter };

Middleware de Autenticação

// src/middleware/auth.ts
import { Context, Next } from "hono";
import { verifyToken } from "../utils/jwt.ts";

export interface AuthContext {
  userId: number;
  email: string;
}

export async function authMiddleware(c: Context, next: Next) {
  const authHeader = c.req.header("Authorization");

  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    return c.json({ error: "Token não fornecido" }, 401);
  }

  const token = authHeader.substring(7);

  try {
    const payload = await verifyToken(token);

    // Adicionar dados do usuário ao contexto
    c.set("user", {
      userId: payload.userId,
      email: payload.email,
    });

    await next();
  } catch (error) {
    if (error instanceof Error && error.message === "Token expirado") {
      return c.json({ error: "Token expirado" }, 401);
    }
    return c.json({ error: "Token inválido" }, 401);
  }
}

Utilitários de Criptografia

// src/utils/crypto.ts
import { encodeBase64, decodeBase64 } from "@std/encoding/base64";

export async function hashPassword(password: string): Promise<string> {
  const encoder = new TextEncoder();
  const data = encoder.encode(password);

  // Gerar salt
  const salt = crypto.getRandomValues(new Uint8Array(16));

  // Derivar chave usando PBKDF2
  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    data,
    "PBKDF2",
    false,
    ["deriveBits"]
  );

  const derivedBits = await crypto.subtle.deriveBits(
    {
      name: "PBKDF2",
      salt,
      iterations: 100000,
      hash: "SHA-256",
    },
    keyMaterial,
    256
  );

  // Combinar salt e hash
  const hashArray = new Uint8Array(derivedBits);
  const combined = new Uint8Array(salt.length + hashArray.length);
  combined.set(salt);
  combined.set(hashArray, salt.length);

  return encodeBase64(combined);
}

export async function comparePassword(
  password: string,
  storedHash: string
): Promise<boolean> {
  const combined = decodeBase64(storedHash);
  const salt = combined.slice(0, 16);
  const storedHashBytes = combined.slice(16);

  const encoder = new TextEncoder();
  const data = encoder.encode(password);

  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    data,
    "PBKDF2",
    false,
    ["deriveBits"]
  );

  const derivedBits = await crypto.subtle.deriveBits(
    {
      name: "PBKDF2",
      salt,
      iterations: 100000,
      hash: "SHA-256",
    },
    keyMaterial,
    256
  );

  const hashArray = new Uint8Array(derivedBits);

  // Comparação em tempo constante
  if (hashArray.length !== storedHashBytes.length) {
    return false;
  }

  let result = 0;
  for (let i = 0; i < hashArray.length; i++) {
    result |= hashArray[i] ^ storedHashBytes[i];
  }

  return result === 0;
}

Testes com Deno

O Deno tem suporte nativo a testes, sem necessidade de bibliotecas externas:

// tests/users.test.ts
import { assertEquals, assertExists } from "@std/assert";
import { describe, it, beforeAll, afterAll } from "@std/testing/bdd";

describe("Users API", () => {
  let testUserId: number;
  let authToken: string;

  beforeAll(async () => {
    // Setup: limpar dados de teste
    await cleanupTestData();
  });

  afterAll(async () => {
    // Cleanup após testes
    await cleanupTestData();
  });

  describe("POST /api/v1/users", () => {
    it("should create a new user", async () => {
      const response = await fetch("http://localhost:8000/api/v1/users", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          email: "test@example.com",
          name: "Test User",
          password: "securePassword123",
        }),
      });

      assertEquals(response.status, 201);

      const user = await response.json();
      assertExists(user.id);
      assertEquals(user.email, "test@example.com");
      assertEquals(user.name, "Test User");

      testUserId = user.id;
    });

    it("should reject duplicate email", async () => {
      const response = await fetch("http://localhost:8000/api/v1/users", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          email: "test@example.com",
          name: "Another User",
          password: "anotherPassword123",
        }),
      });

      assertEquals(response.status, 409);
    });

    it("should validate email format", async () => {
      const response = await fetch("http://localhost:8000/api/v1/users", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          email: "invalid-email",
          name: "Test User",
          password: "securePassword123",
        }),
      });

      assertEquals(response.status, 400);
    });
  });

  describe("POST /api/v1/users/login", () => {
    it("should authenticate valid credentials", async () => {
      const response = await fetch("http://localhost:8000/api/v1/users/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          email: "test@example.com",
          password: "securePassword123",
        }),
      });

      assertEquals(response.status, 200);

      const data = await response.json();
      assertExists(data.token);
      assertExists(data.user);

      authToken = data.token;
    });

    it("should reject invalid password", async () => {
      const response = await fetch("http://localhost:8000/api/v1/users/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          email: "test@example.com",
          password: "wrongPassword",
        }),
      });

      assertEquals(response.status, 401);
    });
  });
});

async function cleanupTestData() {
  // Implementar limpeza de dados de teste
}

Execute os testes:

# Executar todos os testes
deno test --allow-net --allow-env

# Com coverage
deno test --allow-net --allow-env --coverage=coverage/

# Gerar relatório HTML
deno coverage coverage/ --html

Deno vs Node.js: Comparação Atualizada

Com o Deno 2.1, a comparação fica mais equilibrada:

Aspecto Deno 2.1 Node.js 22
TypeScript Nativo, sem config Requer ts-node ou build
Segurança Permissões granulares Acesso total por padrão
npm 100% compatível Nativo
Performance 40% mais rápido (cold start) Baseline
Formatter/Linter Built-in ESLint + Prettier
Testes Nativos Jest/Vitest
Deploy Deno Deploy, compilável Múltiplas opções
Maturidade 4 anos 14 anos

Quando Escolher Deno

Use Deno para:

  • Novos projetos greenfield
  • APIs e microserviços
  • Scripts e automação
  • Projetos com foco em segurança
  • Edge computing (Deno Deploy)

Continue com Node.js para:

  • Projetos legados grandes
  • Quando biblioteca específica não funciona
  • Equipes não familiarizadas com Deno
  • Requisitos empresariais específicos

Conclusão

O Deno 2.1 representa um marco importante na evolução dos runtimes JavaScript. Com compatibilidade total com npm, performance superior e developer experience refinada, finalmente é uma opção viável para produção.

Se você está começando um novo projeto, vale a pena experimentar o Deno. A curva de aprendizado é mínima para quem já conhece Node.js, e os benefícios em segurança e DX são significativos.

Se você se sente inspirado a explorar novas tecnologias JavaScript, recomendo que dê uma olhada em outro artigo: Bun vs Node.js vs Deno: Qual Runtime Escolher em 2025 onde você vai descobrir as diferenças entre os principais runtimes JavaScript.

Bora pra cima! 🦅

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário