TL;DR: Let's build a complete REST API with bunWay—CRUD operations, middleware, error handling, and database integration. If you know Express, you'll feel right at home.
bunWay is an Express-compatible web framework for Bun. Same familiar API, but running on Bun's fast runtime.
Let's build a real API from scratch.
Prerequisites
-
Bun installed (
curl -fsSL https://bun.sh/install | bash) - Basic JavaScript/TypeScript knowledge
- 10 minutes
Step 1: Project Setup
# Create project directory
mkdir bunway-api && cd bunway-api
# Initialize project
bun init -y
# Install bunWay
bun add bunway
Step 2: Basic Server
Create index.ts:
import { bunway, json, cors, logger } from 'bunway'
const app = bunway()
// Middleware
app.use(logger('dev'))
app.use(cors())
app.use(json())
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: Date.now() })
})
// Start server
app.listen(3000, () => {
console.log('API running at http://localhost:3000')
})
Run it:
bun run index.ts
Test it:
curl http://localhost:3000/health
# {"status":"ok","timestamp":1706547200000}
You have a running API.
Step 3: In-Memory Data Store
For this tutorial, we'll use an in-memory store. Replace with your database of choice in production.
Add to index.ts:
// Types
interface User {
id: string
name: string
email: string
createdAt: number
}
// In-memory store
const users: Map<string, User> = new Map()
// Helper to generate IDs
const generateId = () => Math.random().toString(36).substring(2, 9)
Step 4: CRUD Routes
Now let's add the user routes:
// GET all users
app.get('/api/users', (req, res) => {
const allUsers = Array.from(users.values())
res.json(allUsers)
})
// GET single user
app.get('/api/users/:id', (req, res) => {
const user = users.get(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json(user)
})
// POST create user
app.post('/api/users', (req, res) => {
const { name, email } = req.body
// Validation
if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' })
}
// Check for duplicate email
const existingUser = Array.from(users.values()).find(u => u.email === email)
if (existingUser) {
return res.status(409).json({ error: 'Email already exists' })
}
// Create user
const user: User = {
id: generateId(),
name,
email,
createdAt: Date.now()
}
users.set(user.id, user)
res.status(201).json(user)
})
// PUT update user
app.put('/api/users/:id', (req, res) => {
const user = users.get(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
const { name, email } = req.body
// Update fields if provided
if (name) user.name = name
if (email) user.email = email
users.set(user.id, user)
res.json(user)
})
// DELETE user
app.delete('/api/users/:id', (req, res) => {
const user = users.get(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
users.delete(req.params.id)
res.status(204).send('')
})
Step 5: Error Handling
Add a global error handler:
import { bunway, json, cors, logger, errorHandler } from 'bunway'
// ... your routes ...
// 404 handler (add after all routes)
app.use((req, res) => {
res.status(404).json({ error: 'Not found' })
})
// Error handler (add last)
app.use(errorHandler())
Complete Code
Here's the full index.ts:
import { bunway, json, cors, logger, errorHandler } from 'bunway'
const app = bunway()
// Types
interface User {
id: string
name: string
email: string
createdAt: number
}
// In-memory store
const users: Map<string, User> = new Map()
const generateId = () => Math.random().toString(36).substring(2, 9)
// Middleware
app.use(logger('dev'))
app.use(cors())
app.use(json())
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: Date.now() })
})
// GET all users
app.get('/api/users', (req, res) => {
const allUsers = Array.from(users.values())
res.json(allUsers)
})
// GET single user
app.get('/api/users/:id', (req, res) => {
const user = users.get(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json(user)
})
// POST create user
app.post('/api/users', (req, res) => {
const { name, email } = req.body
if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' })
}
const existingUser = Array.from(users.values()).find(u => u.email === email)
if (existingUser) {
return res.status(409).json({ error: 'Email already exists' })
}
const user: User = {
id: generateId(),
name,
email,
createdAt: Date.now()
}
users.set(user.id, user)
res.status(201).json(user)
})
// PUT update user
app.put('/api/users/:id', (req, res) => {
const user = users.get(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
const { name, email } = req.body
if (name) user.name = name
if (email) user.email = email
users.set(user.id, user)
res.json(user)
})
// DELETE user
app.delete('/api/users/:id', (req, res) => {
const user = users.get(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
users.delete(req.params.id)
res.status(204).send('')
})
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Not found' })
})
// Error handler
app.use(errorHandler())
// Start server
app.listen(3000, () => {
console.log('API running at http://localhost:3000')
})
Testing the API
Start the server:
bun run index.ts
Test with curl:
# Create a user
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
# Response: {"id":"abc123","name":"Alice","email":"alice@example.com","createdAt":1706547200000}
# Get all users
curl http://localhost:3000/api/users
# Get single user
curl http://localhost:3000/api/users/abc123
# Update user
curl -X PUT http://localhost:3000/api/users/abc123 \
-H "Content-Type: application/json" \
-d '{"name": "Alice Smith"}'
# Delete user
curl -X DELETE http://localhost:3000/api/users/abc123
Bonus: Adding Authentication Middleware
Here's a simple JWT-style auth middleware:
// Auth middleware
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1]
if (!token) {
return res.status(401).json({ error: 'No token provided' })
}
// In production, verify the token properly
if (token !== 'secret-token') {
return res.status(403).json({ error: 'Invalid token' })
}
// Attach user info to request
req.user = { id: '1', role: 'admin' }
next()
}
// Protected route
app.get('/api/protected', authMiddleware, (req, res) => {
res.json({ message: 'Secret data', user: req.user })
})
Bonus: Rate Limiting
import { bunway, json, cors, logger, rateLimit } from 'bunway'
// Apply to all routes
app.use(rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100 // 100 requests per minute
}))
// Or apply to specific routes
app.post('/api/users', rateLimit({ windowMs: 60000, max: 10 }), (req, res) => {
// Max 10 user creations per minute
})
Bonus: Database Integration
Replace the in-memory store with a real database. Here's a Prisma example:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
app.get('/api/users', async (req, res) => {
const users = await prisma.user.findMany()
res.json(users)
})
app.post('/api/users', async (req, res) => {
const { name, email } = req.body
const user = await prisma.user.create({
data: { name, email }
})
res.status(201).json(user)
})
Project Structure for Larger Apps
bunway-api/
├── src/
│ ├── index.ts # Entry point
│ ├── routes/
│ │ ├── users.ts # User routes
│ │ └── posts.ts # Post routes
│ ├── middleware/
│ │ ├── auth.ts # Auth middleware
│ │ └── validate.ts # Validation middleware
│ ├── services/
│ │ └── user.ts # Business logic
│ └── types/
│ └── index.ts # TypeScript types
├── package.json
└── tsconfig.json
Example route file (src/routes/users.ts):
import { bunway } from 'bunway'
const router = bunway.Router()
router.get('/', (req, res) => {
// Get all users
})
router.get('/:id', (req, res) => {
// Get user by ID
})
export default router
Mount in index.ts:
import userRoutes from './routes/users'
app.use('/api/users', userRoutes)
What's Next?
You now have a working REST API with:
- CRUD operations
- Request logging
- CORS support
- JSON parsing
- Error handling
To go further:
- Add a real database (Prisma, Drizzle, etc.)
- Implement JWT authentication
- Add request validation (Zod, etc.)
- Write tests with
bun test - Deploy to a server or serverless platform
Resources
- bunWay Docs: bunway.jointops.dev
- GitHub: github.com/JointOps/bunway
- Bun Docs: bun.sh/docs
Questions? Drop a comment or open an issue on GitHub.
Tags: #bun #javascript #typescript #api #tutorial #backend #webdev #beginners
Top comments (0)