Guia Pratico do Hono - Framework Web Leve e Rapido para Edge

Intermediario | 40 min leitura | 2025.12.02

O que e o Hono

Hono e um framework web leve e rapido desenvolvido no Japao. Funciona em qualquer runtime JavaScript, incluindo Cloudflare Workers, Deno, Bun, Node.js e AWS Lambda.

flowchart TB
    subgraph Core["Hono Core (~12KB)"]
        Router["Router<br/>(RegExpRouter, TrieRouter)"]
        MW["Middleware Chain"]
        Ctx["Context<br/>(c.req, c.res)"]
    end

    Core --> CF["Cloudflare Workers"]
    Core --> Deno["Deno Deploy"]
    Core --> Bun["Bun"]

    CF --> Node["Node.js"]
    Deno --> Vercel["Vercel"]
    Bun --> Lambda["AWS Lambda"]

Caracteristicas: Compativel com Web Standards API, TypeScript First, Ultra leve

Configuracao

# Criar novo projeto
npm create hono@latest my-app

# Adicionar a projeto existente
npm install hono

# Adaptadores (conforme necessario)
npm install @hono/node-server

Aplicacao Basica

// src/index.ts
import { Hono } from 'hono';

const app = new Hono();

app.get('/', (c) => {
  return c.text('Hello Hono!');
});

app.get('/json', (c) => {
  return c.json({ message: 'Hello JSON!' });
});

export default app;

Metodos de Inicializacao por Runtime

// Cloudflare Workers
export default app;

// Node.js
import { serve } from '@hono/node-server';
serve(app);

// Bun
export default { port: 3000, fetch: app.fetch };

// Deno
Deno.serve(app.fetch);

Roteamento

import { Hono } from 'hono';

const app = new Hono();

// Rotas basicas
app.get('/users', (c) => c.json({ users: [] }));
app.post('/users', (c) => c.json({ created: true }));
app.put('/users/:id', (c) => c.json({ updated: true }));
app.delete('/users/:id', (c) => c.json({ deleted: true }));

// Parametros de caminho
app.get('/users/:id', (c) => {
  const id = c.req.param('id');
  return c.json({ id });
});

// Multiplos parametros
app.get('/posts/:postId/comments/:commentId', (c) => {
  const { postId, commentId } = c.req.param();
  return c.json({ postId, commentId });
});

// Wildcard
app.get('/files/*', (c) => {
  const path = c.req.param('*');
  return c.text(`File path: ${path}`);
});

// Padrao com expressao regular
app.get('/user/:id{[0-9]+}', (c) => {
  const id = c.req.param('id');
  return c.json({ id: parseInt(id) });
});

// Parametro opcional
app.get('/posts/:id?', (c) => {
  const id = c.req.param('id');
  if (id) {
    return c.json({ post: { id } });
  }
  return c.json({ posts: [] });
});

Grupos de Rotas

import { Hono } from 'hono';

const app = new Hono();

// Grupo API
const api = new Hono();

api.get('/users', (c) => c.json({ users: [] }));
api.get('/users/:id', (c) => c.json({ user: {} }));
api.post('/users', (c) => c.json({ created: true }));

// Montar com prefixo
app.route('/api', api);

// Rotas aninhadas
const v1 = new Hono();
const v2 = new Hono();

v1.get('/users', (c) => c.json({ version: 1 }));
v2.get('/users', (c) => c.json({ version: 2 }));

app.route('/api/v1', v1);
app.route('/api/v2', v2);

Middleware

import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { compress } from 'hono/compress';
import { etag } from 'hono/etag';
import { secureHeaders } from 'hono/secure-headers';
import { timing } from 'hono/timing';
import { prettyJSON } from 'hono/pretty-json';

const app = new Hono();

// Middlewares integrados
app.use('*', logger());
app.use('*', cors());
app.use('*', compress());
app.use('*', etag());
app.use('*', secureHeaders());
app.use('*', timing());
app.use('*', prettyJSON());

// Middleware customizado
app.use('*', async (c, next) => {
  const start = Date.now();
  await next();
  const duration = Date.now() - start;
  c.res.headers.set('X-Response-Time', `${duration}ms`);
});

// Aplicar apenas em caminhos especificos
app.use('/api/*', async (c, next) => {
  console.log('API request:', c.req.url);
  await next();
});

Middleware de Autenticacao

import { Hono } from 'hono';
import { jwt } from 'hono/jwt';
import { bearerAuth } from 'hono/bearer-auth';
import { basicAuth } from 'hono/basic-auth';

const app = new Hono();

// Autenticacao JWT
app.use('/api/*', jwt({
  secret: process.env.JWT_SECRET!,
}));

app.get('/api/me', (c) => {
  const payload = c.get('jwtPayload');
  return c.json({ user: payload });
});

// Autenticacao Bearer Token
app.use('/admin/*', bearerAuth({
  token: process.env.ADMIN_TOKEN!,
}));

// Autenticacao Basic
app.use('/dashboard/*', basicAuth({
  username: 'admin',
  password: 'secret',
}));

// Autenticacao customizada
const authMiddleware = async (c: Context, next: Next) => {
  const token = c.req.header('Authorization')?.replace('Bearer ', '');

  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401);
  }

  try {
    const user = await verifyToken(token);
    c.set('user', user);
    await next();
  } catch {
    return c.json({ error: 'Invalid token' }, 401);
  }
};

app.use('/api/*', authMiddleware);

Validacao

Integracao com Zod

import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const app = new Hono();

// Validacao do corpo da requisicao
const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(2).max(100),
  age: z.number().int().positive().optional(),
});

app.post('/users', zValidator('json', createUserSchema), async (c) => {
  const data = c.req.valid('json');
  // data e type-safe: { email: string, name: string, age?: number }
  return c.json({ user: data });
});

// Validacao de query parameters
const listUsersSchema = z.object({
  limit: z.coerce.number().int().min(1).max(100).default(10),
  offset: z.coerce.number().int().min(0).default(0),
  sort: z.enum(['name', 'created_at']).optional(),
});

app.get('/users', zValidator('query', listUsersSchema), async (c) => {
  const { limit, offset, sort } = c.req.valid('query');
  return c.json({ limit, offset, sort });
});

// Validacao de parametros de caminho
const userIdSchema = z.object({
  id: z.string().uuid(),
});

app.get('/users/:id', zValidator('param', userIdSchema), async (c) => {
  const { id } = c.req.valid('param');
  return c.json({ id });
});

// Validacao de headers
const headerSchema = z.object({
  'x-api-key': z.string().min(32),
});

app.use('/api/*', zValidator('header', headerSchema));

Erros de Validacao Customizados

import { zValidator } from '@hono/zod-validator';

app.post(
  '/users',
  zValidator('json', createUserSchema, (result, c) => {
    if (!result.success) {
      return c.json(
        {
          error: {
            code: 'VALIDATION_ERROR',
            message: 'Invalid request body',
            details: result.error.issues.map((issue) => ({
              field: issue.path.join('.'),
              message: issue.message,
            })),
          },
        },
        400
      );
    }
  }),
  async (c) => {
    const data = c.req.valid('json');
    return c.json({ user: data });
  }
);

Hono RPC

Permite comunicacao type-safe entre cliente e servidor.

// server.ts
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const app = new Hono()
  .get('/users', async (c) => {
    const users = await db.users.findMany();
    return c.json({ users });
  })
  .get('/users/:id', async (c) => {
    const id = c.req.param('id');
    const user = await db.users.findById(id);
    return c.json({ user });
  })
  .post(
    '/users',
    zValidator('json', z.object({
      email: z.string().email(),
      name: z.string(),
    })),
    async (c) => {
      const data = c.req.valid('json');
      const user = await db.users.create(data);
      return c.json({ user }, 201);
    }
  );

export type AppType = typeof app;
export default app;
// client.ts
import { hc } from 'hono/client';
import type { AppType } from './server';

const client = hc<AppType>('http://localhost:3000');

// Chamadas de API type-safe
async function main() {
  // GET /users
  const res1 = await client.users.$get();
  const data1 = await res1.json(); // Tipos inferidos

  // GET /users/:id
  const res2 = await client.users[':id'].$get({
    param: { id: '123' },
  });
  const data2 = await res2.json();

  // POST /users
  const res3 = await client.users.$post({
    json: {
      email: 'user@example.com',
      name: 'John Doe',
    },
  });
  const data3 = await res3.json();
}

Upload de Arquivos

import { Hono } from 'hono';

const app = new Hono();

app.post('/upload', async (c) => {
  const body = await c.req.parseBody();
  const file = body['file'];

  if (file instanceof File) {
    const buffer = await file.arrayBuffer();
    const filename = file.name;
    const contentType = file.type;
    const size = file.size;

    // Upload para S3 ou R2
    await uploadToStorage(buffer, filename);

    return c.json({
      filename,
      contentType,
      size,
      url: `https://cdn.example.com/${filename}`,
    });
  }

  return c.json({ error: 'No file uploaded' }, 400);
});

// Upload de multiplos arquivos
app.post('/upload-multiple', async (c) => {
  const body = await c.req.parseBody({ all: true });
  const files = body['files'];

  if (Array.isArray(files)) {
    const results = await Promise.all(
      files.map(async (file) => {
        if (file instanceof File) {
          const buffer = await file.arrayBuffer();
          await uploadToStorage(buffer, file.name);
          return { filename: file.name, size: file.size };
        }
        return null;
      })
    );

    return c.json({ uploaded: results.filter(Boolean) });
  }

  return c.json({ error: 'No files uploaded' }, 400);
});

Tratamento de Erros

import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';

const app = new Hono();

// Classes de erro customizadas
class NotFoundError extends HTTPException {
  constructor(resource: string) {
    super(404, { message: `${resource} not found` });
  }
}

class ValidationError extends HTTPException {
  constructor(details: unknown[]) {
    super(400, {
      message: 'Validation failed',
      cause: { details },
    });
  }
}

// Handler de erros
app.onError((err, c) => {
  console.error('Error:', err);

  if (err instanceof HTTPException) {
    return c.json(
      {
        error: {
          code: err.status === 404 ? 'NOT_FOUND' : 'ERROR',
          message: err.message,
          details: err.cause,
        },
      },
      err.status
    );
  }

  return c.json(
    {
      error: {
        code: 'INTERNAL_ERROR',
        message: 'An unexpected error occurred',
      },
    },
    500
  );
});

// Handler 404
app.notFound((c) => {
  return c.json(
    {
      error: {
        code: 'NOT_FOUND',
        message: 'Route not found',
      },
    },
    404
  );
});

// Exemplo de uso
app.get('/users/:id', async (c) => {
  const user = await db.users.findById(c.req.param('id'));
  if (!user) {
    throw new NotFoundError('User');
  }
  return c.json({ user });
});

Integracao com Cloudflare Workers

// src/index.ts
import { Hono } from 'hono';

type Bindings = {
  DB: D1Database;
  KV: KVNamespace;
  R2: R2Bucket;
  AI: Ai;
};

const app = new Hono<{ Bindings: Bindings }>();

// Banco de dados D1
app.get('/users', async (c) => {
  const { results } = await c.env.DB.prepare(
    'SELECT * FROM users LIMIT 10'
  ).all();
  return c.json({ users: results });
});

// Armazenamento KV
app.get('/cache/:key', async (c) => {
  const key = c.req.param('key');
  const value = await c.env.KV.get(key);
  return c.json({ key, value });
});

app.put('/cache/:key', async (c) => {
  const key = c.req.param('key');
  const { value, ttl } = await c.req.json();
  await c.env.KV.put(key, value, { expirationTtl: ttl });
  return c.json({ success: true });
});

// Armazenamento de objetos R2
app.get('/files/:key', async (c) => {
  const key = c.req.param('key');
  const object = await c.env.R2.get(key);

  if (!object) {
    return c.json({ error: 'Not found' }, 404);
  }

  const headers = new Headers();
  object.writeHttpMetadata(headers);
  headers.set('etag', object.httpEtag);

  return new Response(object.body, { headers });
});

// Workers AI
app.post('/ai/summarize', async (c) => {
  const { text } = await c.req.json();

  const response = await c.env.AI.run('@cf/meta/llama-2-7b-chat-int8', {
    messages: [
      { role: 'system', content: 'Summarize the following text.' },
      { role: 'user', content: text },
    ],
  });

  return c.json({ summary: response.response });
});

export default app;

Testes

// src/index.test.ts
import { describe, it, expect } from 'vitest';
import app from './index';

describe('API Tests', () => {
  it('GET / returns Hello', async () => {
    const res = await app.request('/');
    expect(res.status).toBe(200);
    expect(await res.text()).toBe('Hello Hono!');
  });

  it('GET /users returns user list', async () => {
    const res = await app.request('/users');
    expect(res.status).toBe(200);

    const data = await res.json();
    expect(data).toHaveProperty('users');
  });

  it('POST /users creates a user', async () => {
    const res = await app.request('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: 'test@example.com',
        name: 'Test User',
      }),
    });

    expect(res.status).toBe(201);
    const data = await res.json();
    expect(data.user.email).toBe('test@example.com');
  });

  it('POST /users returns 400 for invalid data', async () => {
    const res = await app.request('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: 'invalid-email',
      }),
    });

    expect(res.status).toBe(400);
  });
});
← Voltar para a lista