Criei meu próprio web framework do zero! (e ele roda com o bun)
Recentemente, numa entrevista de emprego, me deparei com o seguinte desafio: criar um framework web do zero, com o menor número possível de dependências. Esse foi um dos processos mais divertidos e educativos que já passei e, mesmo depois de terminada a fase de testes, segui trabalhando no projeto que resultou no unicorn.web: meu próprio web framework.
Sobre o Bun…
Depois de finalizar o processo, optei por trocar o interpretador por baixo do framework do node para o bun. Primeiro porque queria experimentar esse novo e tão falado interpretador, mas também porque ele prometia aumentar em até 5x a performance de qualquer aplicação javascript e isso muito me interessava.
A primeira coisa que notei foi como a experiência de desenvolvimento com o bun é superior à do node. Com ele você pode rodar arquivos typescript nativamente, sem nenhuma configuração adicional. Possui um resolvedor de módulos realmente fenomenal, que facilita muito a vida na hora de importar e exportar arquivos e uma documentação bastante objetiva e de fácil absorção. Além de já vir com um módulo de testes muito semelhante ao Jest (mas infinitamente mais rápido). Ainda que o Bun não entregasse a performance que ele promete só essas características já o fariam valer a pena e me economizaram algumas horas de trabalho de setup.
Além disso, sua performance é realmente impressionante, sua inicialização extremamente rápida e um teste de carga simples mostrou a capacidade de segurar até 40 mil requests por segundo na minha máquina.
Sobre o framework
Só para ficar claro… este não é um framework para ser usado em um ambiente de produção (ainda). A ideia aqui era escrever algo simples, muito fácil de aprender e utilizar. Um framework leve, para testar conceitos e desenvolver pequenos projetos.
Na prática, isso significava que o usuário deveria ser capaz de subir uma API com apenas 3 ou 4 linhas e esse objetivo foi alcançado! Depois de installar o Bun e adicionar o framework como dependência, com apenas três linhas você consegue criar uma rota GET simples.
bun add @unicorn.web/core
import { UnicornServer } from '@unicorn.web/core'
const server = new UnicornServer();
server.get('/health', () => new Response("I'm working!"));
server.serve(3000);
Se você precisar de rotas mais complexas, basta adicionar a lógica dentro do callback. Também é possível extrair dados da request (query, body, params e headers) adicionando um parâmetro Context
import { UnicornServer } from '@unicorn.web/core'
const server = new UnicornServer();
server.get('/hi', (ctx: Context) => {
return new Reponse(`Hi ${ctx.query.name}!`);
});
server.serve(3000);
Por fim, para rodar sua aplicação, um simples bun run index.ts
resolve todos os seus problemas.
Conclusão
Mais do que tracionar o framework, o objetivo aqui foi entender de vez o que roda por baixo dos frameworks que eu utilizo no trabalho e no dia a dia. Entender como subir um servidor web “cru”, como processar os requests que esse servidor recebe, onde buscar as informações contidas nestas requests e muito mais.
Desafios surgiram no caminho. Um bom exemplo: como fazer matching das rotas que possuem patterns em suas definições (Exemplo: GET /pessoa/:id
) e extrair seus parâmetros? Uma aula de regex e estruturas de dados como tries.
Certamente, me aventurar nesse universo mais “baixo nível” e abandonar as abstrações que frameworks como nest ou adonis nos proporcionam foi muito positivo e me ensinou muita coisa. Como uma desenvolvedora backend, hoje entendo melhor como os servidores web funcionam, o que de fato é o protocolo HTTP e o que está por trás de todo o processo de comunicação entre clientes e servidores no universo web.
Se alguém quiser dar sugestões e contribuir com o projeto, ele é completamente open source, fiquem à vontade para abrir issues e enviar pull requests (ou comentar aqui mesmo rs).