Hono API Layer
Lightweight REST API backend for authentication, database proxy, and file storage.
What is Hono?
Hono is an ultrafast, lightweight web framework. AppLighter uses Hono in apps/server/ for backend operations.
Server Structure
apps/server/
├── src/
│ ├── index.ts # Server entry point
│ ├── db/
│ │ ├── schema.ts # Database schema
│ │ └── store.ts # In-memory/persistent store
│ ├── middleware/
│ │ └── auth.ts # JWT authentication
│ └── routes/
│ ├── auth.ts # Auth endpoints
│ ├── db.ts # Database proxy
│ ├── posts.ts # Posts CRUD
│ ├── profiles.ts # User profiles
│ └── storage.ts # File storage
├── package.json
├── tsconfig.json
└── .env.example
Use Cases
- Authentication - JWT-based sign up, sign in, sign out
- Database Proxy - REST API for Vibecode DB custom adapter
- File Storage - Upload and serve files
- Webhooks - Handle external service callbacks
- Server Actions - Operations requiring secrets
Server Entry Point
// src/index.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { authRoutes } from './routes/auth'
import { dbRoutes } from './routes/db'
import { postsRoutes } from './routes/posts'
import { profilesRoutes } from './routes/profiles'
import { storageRoutes } from './routes/storage'
const app = new Hono()
// Middleware
app.use('*', cors())
// Health check
app.get('/health', (c) => c.json({ status: 'ok' }))
// Mount routes
app.route('/auth', authRoutes)
app.route('/db', dbRoutes)
app.route('/posts', postsRoutes)
app.route('/profiles', profilesRoutes)
app.route('/storage', storageRoutes)
export default {
port: process.env.PORT || 3001,
fetch: app.fetch,
}
Auth Routes
// src/routes/auth.ts
import { Hono } from 'hono'
import { sign, verify } from 'hono/jwt'
import { v4 as uuid } from 'uuid'
const auth = new Hono()
auth.post('/signup', async (c) => {
const { email, password, name } = await c.req.json()
// Create user
const user = {
id: uuid(),
email,
name,
created_at: new Date().toISOString(),
}
// Store user (implement your storage)
await store.users.create(user, password)
// Generate JWT
const token = await sign(
{ sub: user.id, email: user.email },
process.env.JWT_SECRET!
)
return c.json({
user,
session: { access_token: token },
})
})
auth.post('/signin', async (c) => {
const { email, password } = await c.req.json()
// Verify credentials
const user = await store.users.verify(email, password)
if (!user) {
return c.json({ error: 'Invalid credentials' }, 401)
}
// Generate JWT
const token = await sign(
{ sub: user.id, email: user.email },
process.env.JWT_SECRET!
)
return c.json({
user,
session: { access_token: token },
})
})
auth.post('/signout', (c) => {
// Client handles token removal
return c.json({ success: true })
})
auth.get('/session', async (c) => {
const authHeader = c.req.header('Authorization')
if (!authHeader) {
return c.json({ user: null, session: null })
}
const token = authHeader.replace('Bearer ', '')
try {
const payload = await verify(token, process.env.JWT_SECRET!)
const user = await store.users.findById(payload.sub as string)
return c.json({ user, session: { access_token: token } })
} catch {
return c.json({ user: null, session: null })
}
})
export { auth as authRoutes }
Auth Middleware
// src/middleware/auth.ts
import { createMiddleware } from 'hono/factory'
import { verify } from 'hono/jwt'
export const authMiddleware = createMiddleware(async (c, next) => {
const authHeader = c.req.header('Authorization')
if (!authHeader) {
return c.json({ error: 'Unauthorized' }, 401)
}
const token = authHeader.replace('Bearer ', '')
try {
const payload = await verify(token, process.env.JWT_SECRET!)
c.set('userId', payload.sub)
c.set('userEmail', payload.email)
await next()
} catch {
return c.json({ error: 'Invalid token' }, 401)
}
})
Database Proxy Routes
For the custom adapter to communicate with the server:
// src/routes/db.ts
import { Hono } from 'hono'
import { authMiddleware } from '../middleware/auth'
const db = new Hono()
// Apply auth to all routes
db.use('*', authMiddleware)
// Generic query endpoint
db.post('/query', async (c) => {
const { table, operation, filters, data, options } = await c.req.json()
const userId = c.get('userId')
// Execute query based on operation
switch (operation) {
case 'select':
const results = await store[table].find(filters, options)
return c.json({ data: results, error: null })
case 'insert':
await store[table].create({ ...data, user_id: userId })
return c.json({ data: null, error: null })
case 'update':
await store[table].update(filters, data)
return c.json({ data: null, error: null })
case 'delete':
await store[table].delete(filters)
return c.json({ data: null, error: null })
default:
return c.json({ data: null, error: 'Unknown operation' }, 400)
}
})
export { db as dbRoutes }
Storage Routes
// src/routes/storage.ts
import { Hono } from 'hono'
import { authMiddleware } from '../middleware/auth'
const storage = new Hono()
storage.use('*', authMiddleware)
storage.post('/upload', async (c) => {
const formData = await c.req.formData()
const file = formData.get('file') as File
if (!file) {
return c.json({ error: 'No file provided' }, 400)
}
// Upload to S3 or local storage
const url = await uploadFile(file)
return c.json({ url })
})
storage.get('/:filename', async (c) => {
const filename = c.req.param('filename')
const file = await getFile(filename)
if (!file) {
return c.notFound()
}
return new Response(file.data, {
headers: { 'Content-Type': file.contentType },
})
})
export { storage as storageRoutes }
Environment Variables
# Server port
PORT=3001
# JWT secret (min 32 characters)
JWT_SECRET=your-secret-key-min-32-characters
# Database (optional for production)
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# S3 Storage (optional)
S3_BUCKET=your-bucket
S3_REGION=us-east-1
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
Running the Server
# Development
pnpm dev:server
# Or directly
cd apps/server && bun run src/index.ts
The server runs on http://localhost:3001 by default.
Connecting from Mobile
Configure the custom adapter to point to your server:
# apps/mobile/.env
EXPO_PUBLIC_ADAPTER=custom
EXPO_PUBLIC_CUSTOM_API_URL=http://localhost:3001
Deployment
Hono runs anywhere - Bun, Node.js, Cloudflare Workers, Vercel:
# Build for Node.js
cd apps/server && bun build src/index.ts --outdir=dist --target=node
# Deploy to your platform of choice
Next Steps
- Learn about Vibecode DB
- Explore Expo Integration
- Set up AI Generation