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.0Configuraçã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.tsConfiguraçã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.

