Example REST API using @azion/js-auth and @azion/js-api-errors - Hono + TypeScript
This project demonstrates how to build a production-ready REST API for Azion Edge Functions using:
- @azion/js-auth - Authentication library for Azion SSO
- @azion/js-api-errors - Error handling following JSON:API specification
- Hono - Lightweight web framework
- Drizzle ORM - TypeScript ORM for database access
- Zod - Schema validation
- Features
- Quick Start
- Project Structure
- API Endpoints
- Authentication
- Error Handling
- Development
- Deployment
- Troubleshooting
- Related Libraries
- SSO Authentication via session cookies and API tokens
- JSON:API compliant error responses
- Request validation with Zod schemas
- Security middlewares (CORS, secure headers, body limit, timeout)
- Request ID tracing for debugging
- Database integration with AWS RDS Data API
- Runs on both Azion Edge Functions and Bun
# Clone the repository
git clone --recurse-submodules git@github.com:aziontech/js-azion-api-example.git
cd js-azion-api-example
# Install dependencies
bun install
# Copy environment file
cp .env.example .env
# Edit .env with your credentials# SSO Authentication
SSO_MODE=stage # development | stage | production
SSO_GQL_SECRET=your_secret_here # Required for stage/production
# AWS RDS Data API (optional)
RDS_REGION=us-east-1
RDS_RESOURCE_ARN=arn:aws:rds:...
RDS_SECRET_ARN=arn:aws:secretsmanager:...
RDS_DATABASE=your_database# Development server (Bun)
bun run dev
# Server running at http://localhost:3000
# Edge Runtime simulation (Azion)
bun run dev:azion
# Server running at http://localhost:3333js-azion-api-example/
├── src/
│ ├── index.ts # Hono app configuration
│ ├── azion.ts # Edge Functions entry point
│ ├── server.ts # Bun development server
│ ├── config.ts # SSO/Auth configuration
│ ├── env.ts # Environment variable management
│ ├── types.ts # TypeScript types
│ ├── handlers/ # Route handlers
│ │ ├── health.ts # Health check endpoints
│ │ ├── tasks.ts # Tasks CRUD
│ │ └── db-test.ts # Database test endpoints
│ ├── middleware/ # Hono middlewares
│ │ ├── auth.ts # Authentication middleware
│ │ ├── security.ts # Security middlewares
│ │ └── validation.ts # Request validation
│ └── db/ # Database layer
│ ├── config.ts # RDS configuration
│ ├── index.ts # Database client
│ └── schema.ts # Drizzle schema
├── js-azion-auth/ # Authentication library (submodule)
├── js-azion-api-errors/ # Error handling library (submodule)
├── azion/ # Azion configuration
│ └── azion.json # Resource definitions
├── scripts/
│ └── deploy.sh # Deployment script
└── package.json
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
No | Health check |
| GET | /healthz |
No | Health check (Kubernetes) |
| GET | /tasks |
Yes | List all tasks |
| GET | /tasks/:id |
Yes | Get task by ID |
| GET | /db/test |
Yes | List database users |
| POST | /db/test |
Yes | Create database user |
# Health check (public)
curl http://localhost:3000/health
# List tasks (requires auth)
curl -H "Authorization: token YOUR_TOKEN" http://localhost:3000/tasks
# Get task by ID
curl -H "Authorization: token YOUR_TOKEN" http://localhost:3000/tasks/1
# Create user (requires auth + body)
curl -X POST http://localhost:3000/db/test \
-H "Authorization: token YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "John Doe", "email": "john@example.com"}'This API uses @azion/js-auth for authentication. Protected endpoints require one of:
Authorization: token <your-azion-token>
# or
Authorization: Bearer <your-azion-token>Cookie: azsid=<session-id> # production
Cookie: azsid_stg=<session-id> # stage┌─────────┐ ┌──────────────────┐ ┌─────────┐
│ Client │────▶│ Example API │────▶│ SSO │
│ │ │ (Edge Function) │ │ GraphQL │
└─────────┘ └──────────────────┘ └─────────┘
│ │ │
│ 1. Request + │ 2. Validate via │
│ Token/Cookie │ @azion/js-auth │
│ │────────────────────▶│
│ │ │
│ │ 3. User & Account │
│ │◀────────────────────│
│ │ │
│ 4. Response │ │
│◀──────────────────│ │
import { azionAuthMiddleware } from './middleware/auth.ts';
// Apply to protected routes
app.get('/tasks', azionAuthMiddleware, listTasksHandler);This API uses @azion/js-api-errors for standardized error responses following JSON:API specification.
{
"errors": [
{
"code": "10001",
"title": "Not Found",
"detail": "The requested resource was not found.",
"status": "404",
"meta": {
"requestId": "abc123"
}
}
]
}import { APIErrorException, codes, exceptionHandler } from '@azion/js-api-errors';
// Throw specific errors
throw new APIErrorException(codes.NOT_FOUND);
throw new APIErrorException({
errorCode: codes.VALIDATION_ERROR,
field: 'email',
meta: { received: 'invalid' }
});
// Global error handler
app.onError((err, c) => {
return exceptionHandler(err, { request: c.req.raw });
});bun run dev # Start Bun development server
bun run dev:azion # Start Azion Edge Runtime locally
bun run build:azion # Build for Azion deployment
bun run typecheck # Run TypeScript type checking
bun test # Run tests| File | Purpose | Runtime |
|---|---|---|
src/server.ts |
Development server | Bun |
src/azion.ts |
Edge Functions entry | Azion Edge |
src/index.ts |
Hono app (shared) | Both |
In Azion Edge Functions, args are passed via FetchEvent.args, not via Azion.env.get().
The src/env.ts module provides a unified getEnv() function:
- Azion Edge: Reads from
FetchEvent.args(set byazion.ts) - Bun/Node: Reads from
process.env
# Login to Azion (first time)
azion login
# Deploy
./scripts/deploy.shThe script will:
- Load environment variables from
.env - Generate
azion/args.jsonwith secrets - Build with
bun build - Deploy to Azion Edge Functions
bun run build:azion
# Output: dist/azion.jsImportant: Always use
bun build, notazion build. The Azion bundler generates incompatible Node.js imports (node:fs,node:module) that don't work in the Edge Runtime.
# Build
bun run build:azion
# Copy to expected location
mkdir -p .edge
cp dist/azion.js .edge/worker.js
# Deploy
azion deploy --local --skip-build --yesNote: Changes may take 15-30 seconds to propagate globally.
- Propagation takes ~15-30 seconds
- Try
?nocache=timestampto bypass edge cache
Always use bun build --target=browser. The Azion bundler (azion build) adds incompatible node:fs and node:module imports.
Args are passed via FetchEvent.args, not Azion.env.get(). Make sure:
setAzionArgs()is called inazion.ts- Config modules use
getEnv()fromenv.ts
# GitHub Actions example
- name: Deploy to Azion
run: |
cat > .env << EOF
SSO_MODE=${{ vars.SSO_MODE }}
SSO_GQL_SECRET=${{ secrets.SSO_GQL_SECRET }}
EOF
azion login --token ${{ secrets.AZION_TOKEN }}
./scripts/deploy.shAuthentication library for Azion Edge Functions.
Error handling library following JSON:API specification.
MIT