A sample authentication demo using Hono framework with JWT tokens, implemented with Clean Architecture, Separation of Concerns, Pure Functional Programming, and Factory Pattern following official Hono best practices.
- 🔐 JWT-based authentication using Hono's built-in JWT middleware
- 🛡️ Protected routes with middleware
- 🎨 Separate HTML, CSS, and JavaScript files
- 🏗️ Clean Architecture with layered separation
- 🔧 Pure functional programming approach
- 🏭 Hono Factory Pattern for better type inference
- ✅ Zod validation for type-safe request handling
- 📝 Full TypeScript support with strict typing
- 🎯 Dependency injection with pure functions
- 🔗 Multiple Hono instances with
app.route()(official best practice)
This project follows Clean Architecture principles with clear separation of concerns:
src/
├── application/ # Application layer (use cases)
│ ├── login-use-case.ts
│ └── verify-token-use-case.ts
├── domain/ # Domain layer (business logic & entities)
│ ├── interfaces.ts
│ └── user-data.ts
├── infrastructure/ # Infrastructure layer (external dependencies)
│ ├── password-service.ts
│ ├── token-service.ts
│ └── user-repository.ts
├── presentation/ # Presentation layer (routes & handlers)
│ └── routes/
│ ├── auth-routes.ts # Authentication routes with factory pattern
│ ├── user-routes.ts # Protected user routes
│ ├── system-routes.ts # System routes (health, static files)
│ └── index.ts # Main router composition
├── types/ # Type definitions
│ ├── config.ts
│ ├── result.ts
│ ├── user.ts
│ └── validation.ts # Zod validation schemas
└── index.ts # Entry point & dependency composition
public/ # Static files served separately
├── index.html
├── styles.css
└── app.js
This project follows official Hono best practices:
- Factory Pattern: Using
createFactory()for better type inference - Separate Hono Instances: Each route group has its own Hono instance
- Route Mounting: Using
app.route()to mount route groups - Validation: Built-in Hono validator with Zod schemas
- Type Safety: Full TypeScript integration with proper typing
- Pure Functions: All business logic is implemented as pure functions
- Immutability: Data structures are treated as immutable with
readonlyproperties - No Classes: Functions and composition instead of OOP
- Dependency Injection: Through function composition and higher-order functions
- Factory Pattern: Hono's factory pattern for type-safe handler creation
- Node.js (v18 or higher)
- npm or yarn
- Install dependencies:
npm install- Start the development server:
npm run dev- Open your browser and navigate to
http://localhost:9527
- Username:
adminoruser - Password:
password
GET /- Redirects to static HTML pageGET /health- Health checkGET /static/*- Static file serving
POST /auth/login- Login endpoint with Zod validationPOST /auth/verify-token- Token verification with Zod validation
GET /user/protected- Protected route (requires JWT)GET /user/profile- User profile (requires JWT)
// Create routes with factory pattern for better type inference
export const createAuthRoutes = (
login: ReturnType<typeof loginUseCase>,
verifyToken: ReturnType<typeof verifyTokenUseCase>,
jwtSecret: string
) => {
const factory = createFactory()
const app = factory.createApp()
// Validation middleware with Zod
const loginValidator = validator('json', (value, c) => {
const parsed = loginSchema.safeParse(value)
if (!parsed.success) {
return c.json({ error: 'Validation failed', details: parsed.error.issues }, 400)
}
return parsed.data
})
// Handler with factory pattern
const loginHandlers = factory.createHandlers(
loginValidator,
async (c) => {
const requestData = c.req.valid('json') as LoginRequest
const result = await login(requestData, jwtSecret)
return result.success ? c.json(result.data) : c.json({ error: result.error }, 401)
}
)
app.post('/login', ...loginHandlers)
return app
}// Main router composition following Hono best practices
export const createRouter = (login, verifyToken, userRepository, jwtSecret) => {
const factory = createFactory<{ Variables: JwtVariables<User> }>()
const app = factory.createApp()
// Create separate route instances
const systemRoutes = createSystemRoutes()
const authRoutes = createAuthRoutes(login, verifyToken, jwtSecret)
const userRoutes = createUserRoutes(userRepository, jwtSecret)
// Mount routes using app.route() - official Hono best practice
app.route('/', systemRoutes) // System routes at root
app.route('/auth', authRoutes) // Auth routes under /auth
app.route('/user', userRoutes) // User routes under /user
return app
}// Type-safe validation with Zod
export const loginSchema = z.object({
username: z.string().min(1, 'Username is required').max(50, 'Username too long'),
password: z.string().min(6, 'Password must be at least 6 characters')
})
// Automatic type inference
export type LoginRequest = z.infer<typeof loginSchema># Valid login
curl -X POST http://localhost:9527/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}'
# Invalid input (shows Zod validation)
curl -X POST http://localhost:9527/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"","password":"123"}'
# Response: {"error":"Validation failed","details":[...]}# Get user profile
curl -X GET http://localhost:9527/user/profile \
-H "Authorization: Bearer <your-jwt-token>"
# Access protected endpoint
curl -X GET http://localhost:9527/user/protected \
-H "Authorization: Bearer <your-jwt-token>"curl -X POST http://localhost:9527/auth/verify-token \
-H "Content-Type: application/json" \
-d '{"token":"<your-jwt-token>"}'npm run dev- Start development server with hot reloadnpm run build- Build for productionnpm start- Start production server
- Separation of Concerns: Each layer has a single responsibility
- Dependency Inversion: High-level modules don't depend on low-level modules
- Testability: Pure functions are easy to test in isolation
- Maintainability: Changes in one layer don't affect others
- Scalability: Easy to add new features without breaking existing code
- Type Safety: Factory pattern provides excellent TypeScript inference
- Modular Routes: Separate Hono instances for different route groups
- Clean Mounting: Using
app.route()for organized route structure - Validation: Built-in validator with Zod for type-safe request handling
- Performance: Efficient route organization and middleware application
- Predictability: Pure functions always return the same output for the same input
- Immutability: No side effects or mutation of state
- Composability: Functions can be easily composed together
- Testability: No mocking required for pure functions
- Concurrency: Safe for concurrent execution
This is a demo project. For production use:
- Use environment variables for JWT secrets
- Use a proper database instead of in-memory storage
- Implement rate limiting
- Add input validation and sanitization (✅ partially implemented with Zod)
- Use HTTPS
- Implement proper error handling
- Add comprehensive logging
- Add request/response validation middleware
- Implement proper CORS configuration
- Better Type Inference: Hono's factory pattern provides superior TypeScript support
- Consistent Typing: Avoid repetitive type declarations across route handlers
- Official Best Practice: Recommended by Hono documentation for larger applications
- Modular Organization: Each feature has its own route group
- Middleware Isolation: Apply middleware only where needed (e.g., JWT on user routes only)
- Scalability: Easy to add new route groups without affecting existing ones
- Runtime Safety: Validates data at runtime, not just compile time
- Better Error Messages: Detailed validation errors for debugging
- Schema-First: Define once, use everywhere with type inference
- Future Proof: Easy to extend with complex validation rules
- Testability: Business logic separated from framework concerns
- Maintainability: Clear boundaries between layers
- Framework Independence: Could easily switch from Hono to another framework
- Scalability: Easy to add features without breaking existing code
├── domain/ → Core business logic (entities, interfaces)
├── application/ → Use cases (business operations)
├── infrastructure/ → External concerns (password hashing, JWT, data)
├── presentation/ → HTTP layer (routes, validation, handlers)
└── types/ → Shared type definitions and schemas
This structure ensures that:
- Domain contains pure business logic with no external dependencies
- Application orchestrates business operations
- Infrastructure handles technical concerns
- Presentation deals with HTTP-specific concerns
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Follow the existing patterns:
- Use factory pattern for new routes
- Add Zod validation for new endpoints
- Maintain pure functional approach
- Keep Clean Architecture layer separation
- Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Hono - Fast web framework following best practices
- Hono Factory - Factory pattern for better type inference
- Hono JWT - Built-in JWT middleware
- Hono Validator - Built-in validation middleware
- Zod - TypeScript-first schema validation
- bcryptjs - Password hashing
- TypeScript - Type safety with strict configuration
Made with ❤️ for Hong Kong films