Orionjs provides a clean and type-safe way to define HTTP routes through the @Routes()
and @Route()
decorators. This approach allows you to organize your API endpoints in controller classes with dependency injection support.
Creating a Routes Controller
A routes controller is a class decorated with @Routes()
that contains methods decorated with @Route()
to define individual endpoints:
import { Route, Routes, createRoute } from '@orion-js/http'
import { Inject } from '@orion-js/services'
import { ExampleService } from '../services/ExampleService'
@Routes()
export default class ExampleRoutes {
@Inject(() => ExampleService)
private exampleService: ExampleService
@Route()
example = createRoute({
path: '/example/:exampleId',
method: 'get',
resolve: async (req) => {
const { exampleId } = req.params
return {
body: await this.exampleService.getAExample(exampleId)
}
}
})
}
Route Definition Options
The createRoute()
function accepts the following options:
@Route()
exampleRoute = createRoute({
path: '/api/users', // Path of the endpoint
method: 'post', // HTTP method (get, post, put, delete, all)
bodyParams: { // Schema for body parameters (optional)
name: { type: String },
email: { type: String }
},
queryParams: { // Schema for query parameters (optional)
limit: { type: Number }
},
returns: { // Schema for return value (optional)
userId: { type: String }
},
middlewares: [authMiddleware], // Express middlewares to apply (optional)
resolve: async (req) => {
// Route implementation
return {
body: { result: 'success' }
}
}
})
Available Options
Option | Type | Description |
---|
path | string | URL path pattern for the route (supports Express path parameters) |
method | 'get' | 'post' | 'put' | 'delete' | 'all' | HTTP method to handle |
bodyParams | Object | Schema | Schema for validating request body |
queryParams | Object | Schema | Schema for validating query parameters |
returns | Object | Schema | Schema for validating response body |
middlewares | Array<RequestHandler> | Express middlewares to apply to this route |
resolve | Function | Function that handles the request and returns a response |
app | express.Application | Optional custom Express app instance |
Route Handlers
Route handlers (the resolve
function) receive the request object and any middleware-injected properties:
@Route()
getUsers = createRoute({
path: '/users',
method: 'get',
resolve: async (req) => {
const users = await this.userService.getUsers()
return {
body: users
}
}
})
Handler Parameters
Route handlers typically receive:
req
: Express Request object with params, query, and body
- Any context injected by middleware (like viewer, authenticated user)
Return Values
Route handlers should return an object with:
return {
statusCode: 201, // Optional status code (defaults to 200)
headers: {'X-Custom-Header': 'value'}, // Optional headers
body: {result: 'success'} // Response body (will be JSON-serialized if object)
}
You can also access the Express response object through the request:
@Route()
downloadFile = createRoute({
path: '/download/:fileId',
method: 'get',
resolve: async (req) => {
const res = req.res
res.setHeader('Content-Type', 'application/pdf')
res.setHeader('Content-Disposition', 'attachment; filename="report.pdf"')
const fileStream = await this.fileService.getFileStream(req.params.fileId)
fileStream.pipe(res)
// No body needed when using res directly
return {}
}
})
Route Parameters
You can access route parameters through the request object, and they will be type-safe if you specify the path with parameter placeholders:
// Route defined as: /users/:userId/posts/:postId
@Route()
getPost = createRoute({
path: '/users/:userId/posts/:postId',
method: 'get',
resolve: async (req) => {
const {userId, postId} = req.params
const post = await this.postService.getPost(userId, postId)
return {
body: post
}
}
})
Query Parameters
You can define and validate query parameters using the queryParams
option:
// GET /search?term=example&limit=10
@Route()
search = createRoute({
path: '/search',
method: 'get',
queryParams: {
term: { type: String },
limit: { type: Number, optional: true }
},
resolve: async (req) => {
// req.query will be validated and typed according to schema
const {term, limit} = req.query
const results = await this.searchService.search(term, limit || 10)
return {
body: results
}
}
})
Request Body
You can define and validate request body parameters using the bodyParams
option:
@Route()
createUser = createRoute({
path: '/users',
method: 'post',
bodyParams: {
name: { type: String },
email: { type: String },
age: { type: Number, optional: true }
},
returns: {
userId: { type: String },
createdAt: { type: Date }
},
resolve: async (req) => {
// req.body will be validated and typed according to schema
const userData = req.body
const newUser = await this.userService.createUser(userData)
return {
statusCode: 201,
body: newUser // Response will be validated against returns schema
}
}
})
Error Handling
Orionjs automatically handles errors thrown in route handlers:
@Route()
getProtectedResource = createRoute({
path: '/protected-resource',
method: 'get',
resolve: async (req) => {
if (!req.headers.authorization) {
throw new Error('Unauthorized')
// Or use a custom error class
// throw new AuthenticationError('Missing authentication')
}
return {
body: await this.resourceService.getResource()
}
}
})
Example: File Uploads
For handling file uploads, you could use the multer
middleware:
import multer from 'multer'
const upload = multer({ dest: 'uploads/' })
@Routes()
export class FileRoutes {
@Inject(() => FileService)
private fileService: FileService
@Route()
uploadFile = createRoute({
path: '/upload',
method: 'post',
middlewares: [upload.single('file')],
resolve: async (req) => {
// Access the file via req.file
const fileInfo = await this.fileService.saveFile(req.file)
return {
statusCode: 201,
body: fileInfo
}
}
})
}
CORS and Security
There are two ways to apply middleware in Orionjs:
1. Route-Specific Middleware
Apply middleware to specific routes using the middlewares
option:
import cors from 'cors'
@Routes()
export class ApiRoutes {
@Route()
getData = createRoute({
path: '/api/data',
method: 'get',
middlewares: [
cors({
origin: 'https://example.com',
methods: ['GET'],
allowedHeaders: ['Content-Type', 'Authorization']
})
],
resolve: async (req) => {
return {
body: await this.dataService.getData()
}
}
})
}
2. Global Middleware
Apply middleware to all routes using the HTTP app:
import {app} from '@orion-js/http'
import helmet from 'helmet'
import cors from 'cors'
// Apply security headers
app.use(helmet())
// Apply CORS for all routes
app.use(cors({
origin: ['https://example.com', 'https://admin.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}))
Custom Response Types
You can define custom response formats for non-JSON responses:
@Route()
getXML = createRoute({
path: '/data.xml',
method: 'get',
resolve: async (req) => {
const xml = await this.xmlService.generateXML()
return {
headers: {
'Content-Type': 'application/xml'
},
body: xml
}
}
})
Authentication and Authorization
You can implement authentication and authorization using middleware:
// Auth middleware
const authMiddleware = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1]
if (!token) {
return res.status(401).json({error: 'No token provided'})
}
try {
const user = await verifyToken(token)
req.user = user // Attach user to request
next()
} catch (error) {
return res.status(401).json({error: 'Invalid token'})
}
}
@Routes()
export class UserRoutes {
@Inject(() => UserService)
private userService: UserService
@Route()
getProfile = createRoute({
path: '/profile',
method: 'get',
middlewares: [authMiddleware],
resolve: async (req) => {
// req.user is guaranteed to exist from the authMiddleware
return {
body: await this.userService.getUserProfile(req.user._id)
}
}
})
}
Starting the HTTP Server
To start the HTTP server:
import {startServer} from '@orion-js/http'
// Start HTTP server
startServer({
port: 3000,
path: '/',
onStart: () => {
console.log('Server started on port 3000')
}
})
Best Practices
-
Organize by Domain: Group related routes in the same controller class.
-
Use Parameter Validation: Define schema for query, body, and response parameters.
-
Keep Route Handlers Simple: Focus on request handling logic, move business logic to services.
-
Leverage Dependency Injection: Use @Inject(() => Service)
for clean service integration.
-
Implement Proper Error Handling: Catch and handle expected errors appropriately.
-
Apply Security Middleware: Use middleware for authentication, CSRF protection, etc.
-
Clear Status Codes: Use appropriate HTTP status codes for different responses.
-
Document Your API: Add clear descriptions for each endpoint and its parameters.
-
Follow RESTful Principles: Use appropriate HTTP methods for different operations.
-
Implement Rate Limiting: Protect against abuse with rate limiting middleware.