waiter-api
v0.1.1
Published
WAITER is a fast, secure and simple TypeScript API library.
Readme
WAITER
WAITER e uma biblioteca TypeScript para criar APIs de forma rapida, segura, simples e legivel.
Ela foi pensada para duas pessoas ao mesmo tempo:
- quem esta aprendendo o que e uma API e como ela funciona
- quem ja sabe construir sistemas e quer uma base limpa para criar rapido sem perder qualidade
O objetivo e ser uma biblioteca que voce consiga entender em poucos minutos, usar em poucas linhas e levar para um projeto serio sem sentir que precisou trocar tudo depois.
Filosofia
WAITER tenta seguir 4 ideias simples:
- ser facil de ler
- ser facil de aprender
- ser segura por padrao
- continuar pequena o suficiente para nao virar uma biblioteca pesada
Isso significa que a API foi desenhada para parecer natural. Voce escreve o que quer que aconteca e a biblioteca cuida do resto: rota, entrada, resposta, erro, seguranca e organizacao.
O que e uma API, em palavras simples
Se voce nunca viu uma API, pense assim:
- o cliente faz uma pergunta para o servidor
- a pergunta chega como uma requisicao
- o servidor decide qual regra executar
- a regra processa os dados
- o servidor devolve uma resposta
WAITER organiza exatamente esse caminho.
Instalacao
npm install waiter-apiSe voce estiver trabalhando dentro do proprio repositorio, instale as dependencias do projeto com:
npm installPrimeiro exemplo
Este e o exemplo mais simples possivel.
import { createWaiter } from "waiter-api";
const app = createWaiter();
app.get("/hello/:name", ({ params }) => {
return {
message: `Hello, ${params.name}!`
};
});
const response = await app.handle(new Request("http://localhost/hello/waiter"));
console.log(await response.json());O que aconteceu aqui
createWaiter()cria a aplicacaoapp.get(...)registra uma rota que responde a requisicoesGET"/hello/:name"define uma rota com parametro dinamicoparams.namepega o valor da URLapp.handle(request)executa a aplicacao contra uma requisicao- o retorno vira uma
Responsepronta para ser lida como JSON
Como ler a WAITER mentalmente
Pense na WAITER como 5 pecas:
- configuracao global
- rotas
- middleware
- contexto da requisicao
- resposta e erro
Voce nao precisa entender tudo de uma vez. O normal e comecar por rotas, depois middleware, depois validacao e auth.
createWaiter
Essa e a porta de entrada da biblioteca.
const app = createWaiter({
notFoundMessage: "Rota nao encontrada",
maxBodySizeBytes: 1024 * 1024
});Por que existe
Porque quase toda API precisa de algumas regras globais:
- mensagem padrao quando a rota nao existe
- limite maximo do corpo da requisicao
- headers de seguranca padrao
Configuracao disponivel
notFoundMessage: personaliza a resposta de rota nao encontradamaxBodySizeBytes: limita o tamanho do corpo da requisicaosecurityHeaders: liga, desliga ou personaliza headers de segurancaerrorHandler: reservado para fluxos avancados de tratamento de erro
Rotas
Rotas sao os caminhos que sua API entende.
app.get("/users", () => {
return { ok: true };
});
app.post("/users", () => {
return { created: true };
});
app.put("/users/:id", ({ params }) => {
return { updated: params.id };
});Metodos disponiveis
getpostputpatchdeleteoptionsheadonpara registrar qualquer metodo manualmente
Por que isso existe
Porque uma API fala por verbos HTTP:
GETpara lerPOSTpara criarPUTePATCHpara atualizarDELETEpara remover
WAITER deixa isso direto e legivel.
Parametros de rota
Parametros servem para capturar partes variaveis da URL.
app.get("/users/:id", ({ params }) => {
return {
userId: params.id
};
});Se a requisicao for GET /users/42, entao params.id vira 42.
Por que isso existe
Porque muitas APIs trabalham com recursos identificados por um valor dinamico:
- usuario por id
- produto por slug
- pedido por numero
Contexto da requisicao
Toda rota recebe um contexto. Ele e o objeto que carrega tudo que a sua regra precisa saber.
app.get("/debug", ({ request, url, params, query }) => {
return {
method: request.method,
path: url.pathname,
params,
query: Object.fromEntries(query.entries())
};
});Campos principais
request: a requisicao originalurl: a URL analisadaparams: parametros da rotaquery: query string da URLauth: estado da autenticacaovalidation: dados validados por middlewarestate: espaco livre para guardar dados da requisicaotext(): le o corpo como textojson(): le o corpo como JSONformData(): le o corpo como formulariorespond(): converte um valor emResponse
Por que isso existe
Porque a sua rota nao deve precisar reinventar tudo sempre. O contexto centraliza o que ja foi coletado pela biblioteca.
Respostas
A WAITER aceita varios formatos de retorno.
app.get("/plain", () => {
return "hello";
});
app.get("/json", () => {
return { message: "hello" };
});
app.get("/raw", () => {
return new Response("raw response");
});Como a biblioteca interpreta isso
- string vira texto puro
- objeto, numero e boolean viram JSON
Responsepassa diretonulleundefinedviram resposta sem corpo
Por que isso existe
Porque o retorno de uma rota deve ser simples de escrever. O desenvolvedor nao deveria precisar montar uma Response toda hora.
Middleware
Middleware e um passo que roda antes da rota final.
app.use(async (_context, next) => {
console.log("antes");
const result = await next();
console.log("depois");
return result;
});Para que serve
Middleware e muito util para coisas que precisam acontecer em varias rotas:
- log
- autenticacao
- validacao
- rastreamento
- tempo de resposta
Ordem de execucao
- a requisicao entra
- os middlewares rodam na ordem em que foram registrados
- a rota executa
- a resposta volta
Regra importante
next() so deve ser chamado uma vez dentro do mesmo middleware.
Validacao de entrada
Validacao serve para garantir que os dados recebidos estao no formato esperado.
WAITER usa Zod para isso.
import { z } from "zod";
import { createValidationMiddleware, createWaiter } from "waiter-api";
const app = createWaiter();
app.use(
createValidationMiddleware({
body: z.object({
name: z.string().min(3)
})
})
);
app.post("/users", ({ validation }) => {
const body = validation.body as { name: string };
return {
created: true,
name: body.name
};
});O que isso faz
- le o corpo da requisicao
- valida com o schema
- guarda o resultado validado em
validation.body - se falhar, responde com erro 400 e
VALIDATION_ERROR
Por que isso existe
Porque dados invalidos sao uma das maiores fontes de bugs e inseguranca em API.
Boas praticas de validacao
- valide sempre dados externos
- use schemas pequenos e claros
- defina mensagens e regras objetivas
- trate entrada como nao confiavel ate provar o contrario
Autenticacao Bearer
Bearer auth e um jeito simples de proteger rotas usando um token no header authorization.
import { createBearerAuth, createWaiter } from "waiter-api";
const app = createWaiter();
app.use(
createBearerAuth({
tokens: ["demo-token"]
})
);
app.get("/secure", ({ auth }) => {
return {
authenticated: auth.isAuthenticated,
token: auth.token
};
});Como usar
Envie o header:
Authorization: Bearer demo-tokenO que acontece
- a WAITER le o header
- verifica se o esquema e
Bearer - valida se o token esta na lista permitida
- marca
auth.isAuthenticated = true - libera o acesso para a rota
Por que isso existe
Porque muitas rotas nao podem ser publicas.
Boas praticas de auth
- nunca guarde token real no codigo fonte
- use variaveis de ambiente
- valide permissao alem de autenticacao quando necessario
- trate rota protegida como rota sensivel
HttpError
HttpError e a forma padrao de sinalizar erros conhecidos.
import { HttpError } from "waiter-api";
app.get("/admin", () => {
throw new HttpError("Forbidden", {
status: 403,
code: "FORBIDDEN"
});
});Por que usar
Porque nem todo erro e inesperado. Muitas vezes voce quer dizer claramente:
- nao autorizado
- proibido
- recurso nao encontrado
- validacao falhou
Formato da resposta
WAITER devolve algo assim:
{
"error": {
"message": "Forbidden",
"code": "FORBIDDEN",
"details": null
}
}Boas praticas de erro
- use
HttpErrorpara erros esperados - deixe erros inesperados virarem
500 - seja claro na mensagem
- nao exponha segredo interno em mensagens de erro
Seguranca padrao
WAITER adiciona headers de seguranca nas respostas por padrao.
Headers incluidos por default:
x-content-type-optionsx-frame-optionsreferrer-policypermissions-policycross-origin-opener-policycross-origin-resource-policy
Por que isso existe
Porque varias protecoes basicas nao devem depender da lembranca do desenvolvedor.
Como personalizar
const app = createWaiter({
securityHeaders: {
"content-security-policy": "default-src 'none'"
}
});Como desativar
const app = createWaiter({
securityHeaders: false
});Limite de corpo da requisicao
Voce pode limitar o tamanho do corpo que a API aceita.
const app = createWaiter({
maxBodySizeBytes: 1024 * 1024
});Por que isso existe
Porque corpo de requisicao muito grande pode causar:
- consumo excessivo de memoria
- lentidao
- abuso de entrada
Boa pratica
Defina limites coerentes com o tipo de endpoint:
- formulario simples: limite pequeno
- upload ou payload grande: limite especifico e consciente
listen
Se voce quiser rodar a biblioteca como servidor HTTP de verdade, use listen.
const app = createWaiter();
app.get("/health", () => ({ ok: true }));
await app.listen(3000);Por que isso existe
Porque a biblioteca nao serve apenas para testes ou frameworks externos. Ela tambem consegue iniciar o servidor HTTP diretamente.
Fluxo recomendado para iniciantes
Se voce estiver comecando agora, siga esta ordem:
- crie a aplicacao com
createWaiter() - registre uma rota
get - retorne um objeto simples
- use
paramspara rotas dinamicas - adicione middleware
- adicione validacao
- depois adicione auth
Exemplo completo e simples
import { z } from "zod";
import {
HttpError,
createBearerAuth,
createValidationMiddleware,
createWaiter
} from "waiter-api";
const app = createWaiter({
notFoundMessage: "Rota nao existe",
maxBodySizeBytes: 1024 * 1024
});
app.use(createBearerAuth({ tokens: ["demo-token"] }));
app.use(
createValidationMiddleware({
body: z.object({
name: z.string().min(3)
})
})
);
app.post("/users", ({ auth, validation }) => {
if (!auth.isAuthenticated) {
throw new HttpError("Unauthorized", {
status: 401,
code: "UNAUTHORIZED"
});
}
const body = validation.body as { name: string };
return {
created: true,
name: body.name,
token: auth.token
};
});Boas praticas gerais
- mantenha as rotas pequenas
- valide tudo que vier de fora
- use nomes claros para rotas e variaveis
- prefira erros tipados quando o problema for esperado
- separe auth, validacao e regra de negocio
- trate
requestcomo dado nao confiavel - use
stateapenas quando precisar compartilhar informacao entre middlewares e rota
Para quem ja tem experiencia
Se voce ja programa APIs, a ideia da WAITER e te dar uma superficie pequena e previsivel.
Voce nao precisa abrir dez camadas para fazer uma rota simples, mas ainda pode estruturar a aplicacao com:
- middlewares reutilizaveis
- validacao padronizada
- auth centralizada
- erros consistentes
- defaults seguros
Scripts do projeto
npm run buildcompila a bibliotecanpm run testexecuta a suite de testesnpm run lintvalida o estilo e os tipos suportados pelo ESLintnpm run typecheckverifica tipos sem gerar saida
O que vem depois
Se quiser evoluir a WAITER, os proximos passos naturais sao:
- tipagem mais forte para
validation.bodypor rota - sistema de roles e permissao
- rate limit
- logs estruturados
- plugins
- geracao de documentacao automatica
Resumo curto
WAITER existe para ser uma biblioteca de API que voce entende rapido, escreve rapido e consegue levar para producao sem parecer um brinquedo.
