- TypeScript 100%
| src | ||
| tests | ||
| .gitignore | ||
| bun.lock | ||
| drizzle.config.ts | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
Bapo API
A real-time chat application API built with a modern, performant TypeScript-native stack.
Tech Stack
Runtime — Bun
Bun is a fast all-in-one JavaScript runtime with native TypeScript support. There is no compilation step — files run directly. It also ships with a built-in package manager, .env loader, and password hashing utilities (Bun.password) used for bcrypt hashing.
Framework — Hono
Lightweight and ultrafast web framework built for the edge with first-class TypeScript support. Used for:
- HTTP routing and middleware chaining
- JWT signing/verification via
hono/jwt - WebSocket upgrade handling via
hono/bun'screateBunWebSocket
ORM — Drizzle ORM
Type-safe, schema-first ORM for SQL databases. Drizzle infers full TypeScript types from the schema, so query results are always typed. Ships with drizzle-kit for generating and running migrations.
Database driver — postgres.js
High-performance PostgreSQL client for Node/Bun. Used directly by Drizzle as the underlying connection layer.
Database — PostgreSQL
Relational database. Stores users, chats, messages, and the user↔chat join table. All primary keys are UUIDs generated by PostgreSQL (gen_random_uuid()).
Project Structure
src/
├── db/
│ ├── schema.ts # Table definitions (users, chats, messages, user_chats)
│ ├── index.ts # postgres.js client + Drizzle instance
│ └── migrations/ # SQL files generated by drizzle-kit
├── middleware/
│ └── auth.ts # JWT bearer middleware + verifyToken helper
├── routes/
│ ├── auth.ts # /api/auth (register, login, logout)
│ ├── users.ts # /api/user
│ ├── chats.ts # /api/chats
│ └── messages.ts # /api/messages (HTTP + WebSocket)
└── index.ts # App entry point / Bun.serve config
Setup
Prerequisites
- Bun >= 1.0
- A running PostgreSQL instance
Install dependencies
bun install
Configure environment
Fill in the values in .env:
DATABASE_URL=postgresql://user:password@localhost:5432/bapo
JWT_SECRET=change-this-to-a-long-random-secret
PORT=3000
Run migrations
bun db:generate # generates SQL from schema
bun db:migrate # applies migrations to the database
Start the server
bun dev # development with hot reload
bun start # production
Environment Variables
| Variable | Description | Required |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | ✅ |
JWT_SECRET |
Secret key used to sign JWT tokens | ✅ |
PORT |
Port to listen on (default: 3000) |
❌ |
API Routes
Auth
| Method | Path | Description | Auth |
|---|---|---|---|
POST |
/api/auth/register |
Register a new user, returns JWT | — |
POST |
/api/auth/login |
Login with credentials, returns JWT | — |
DELETE |
/api/auth/logout |
Logout (stateless — client discards token) | ✅ Bearer |
User
| Method | Path | Description | Auth |
|---|---|---|---|
GET |
/api/user |
Get the authenticated user's profile | ✅ Bearer |
Chats
| Method | Path | Description | Auth |
|---|---|---|---|
GET |
/api/chats |
List all chats the user belongs to | ✅ Bearer |
GET |
/api/chats/:id |
Get a specific chat | ✅ Bearer |
POST |
/api/chats |
Create a new chat (creator is auto-joined) | ✅ Bearer |
PATCH |
/api/chats/:id |
Update the chat name | ✅ Bearer |
DELETE |
/api/chats/:id |
Delete a chat | ✅ Bearer |
POST |
/api/chats/:id/users |
Add a user to a chat by username |
✅ Bearer |
DELETE |
/api/chats/:id/users/:userId |
Remove a user from a chat. Auto-deletes the chat if no members remain | ✅ Bearer |
Messages
| Method | Path | Description | Auth |
|---|---|---|---|
GET |
/api/messages?chatId= |
Get message history for a chat | ✅ Bearer |
WS |
/api/messages/ws?chatId=&token= |
Real-time messaging via WebSocket | ✅ Token param |
PATCH |
/api/messages?id= |
Update a message's content | ✅ Bearer |
DELETE |
/api/messages?id= |
Delete a message | ✅ Bearer |
Note on WebSocket auth: The browser
WebSocketAPI does not support custom headers on the upgrade request. The JWT must be passed as atokenquery parameter instead of aBearerheader.
Data Models
User
{
"id": "uuid",
"username": "string",
"createdAt": "timestamp",
"updatedAt": "timestamp"
}
passwordHashis stored but never returned by any endpoint.
Chat
{
"id": "uuid",
"name": "string",
"createdAt": "timestamp",
"updatedAt": "timestamp"
}
Message
{
"id": "uuid",
"content": "string",
"userId": "uuid",
"chatId": "uuid",
"createdAt": "timestamp",
"updatedAt": "timestamp"
}
user_chats (join table)
| Column | Type |
|---|---|
user_id |
UUID (FK → users) |
chat_id |
UUID (FK → chats) |
Scripts
| Command | Description |
|---|---|
bun dev |
Start with hot reload |
bun start |
Start in production mode |
bun db:generate |
Generate migration SQL from schema changes |
bun db:migrate |
Apply pending migrations |
bun db:studio |
Open Drizzle Studio (visual DB browser) |
Rotas
- POST /api/auth/register
- POST /api/auth/login
- DELETE /api/auth/logout
- GET /api/user
- GET /api/chats
- GET /api/chats/{id}
- POST /api/chats
- PATCH /api/chats/{id}
- DELETE /api/chats/{id}
- POST /api/chats/{id}/users
- DELETE /api/chats/{id}/users/{userId}
- GET /api/messages
- WS /api/messages/ws
- PATCH /api/messages
- DELETE /api/messages