Routing

The Routing module in Orchestr provides a powerful, Laravel-inspired routing system for Node.js applications. It offers an elegant API for defining routes, handling HTTP requests, and managing application flow through middleware.

Overview

The Routing module consists of five core classes that work together to provide a complete routing solution:

Router

The main routing engine that registers routes and dispatches requests

Route

Represents individual routes with their methods, URIs, and actions

Controller

Base class for organizing route logic into controller classes

Request

HTTP request wrapper providing convenient access to request data

Response

HTTP response wrapper for building and sending responses

The routing system is inspired by Laravel's routing API, providing a familiar and elegant syntax while leveraging TypeScript's type safety.

Key Features

Fluent Route API

Route.get(), Route.post(), Route.put() and more for clean route definition

Dynamic Parameters

Pattern matching for route parameters and wildcards

Controller Support

Organize route logic in controller classes with dependency injection

Middleware Pipeline

Global and route-level middleware support

Route Groups

Share attributes across multiple routes

Named Routes

Easy URL generation with route names

File-Based Routes

Auto-discovery and organization of route files

Facade Pattern

Static access through Route facade

Basic Routing

Routes are registered using HTTP verb methods on the Router class. The most common way to define routes is through the Route facade.

Route Registration

Always register routes in your route files or service providers before the application boots. The Route facade automatically resolves the underlying Router service from the container.

Simple Routes

import { Route } from '@orchestr-sh/orchestr';

// Basic GET route
Route.get('/', async (req, res) => {
  return res.send('Hello World');
});

// POST route
Route.post('/users', async (req, res) => {
  const data = req.all();
  return res.status(201).json({ user: data });
});

// PUT route
Route.put('/users/:id', async (req, res) => {
  const id = req.routeParam('id');
  const data = req.all();
  return res.json({ message: `Updated user ${id}`, data });
});

// DELETE route
Route.delete('/users/:id', async (req, res) => {
  const id = req.routeParam('id');
  return res.json({ message: `Deleted user ${id}` });
});

Available HTTP Methods

// GET - Retrieve resources
Route.get('/users', handler);

// POST - Create resources
Route.post('/users', handler);

// PUT - Update/replace resources
Route.put('/users/:id', handler);

// PATCH - Partially update resources
Route.patch('/users/:id', handler);

// DELETE - Remove resources
Route.delete('/users/:id', handler);

// OPTIONS - Get allowed methods
Route.options('/users', handler);

// HEAD - Get headers only
Route.head('/users', handler);

// Match multiple methods
Route.match(['GET', 'POST'], '/users', handler);

// Any method
Route.any('/webhook', handler);

Route Parameters

Route parameters allow you to capture dynamic values from URLs:

// Required parameter
Route.get('/users/:id', async (req, res) => {
  const id = req.routeParam('id'); // '123'
  return res.json({ user: { id } });
});

// Multiple parameters
Route.get('/users/:id/posts/:postId', async (req, res) => {
  const userId = req.routeParam('id');
  const postId = req.routeParam('postId');
  return res.json({ userId, postId });
});

// Optional parameter with ?
Route.get('/users/:id/posts/:postId?', async (req, res) => {
  const id = req.routeParam('id');      // Required
  const postId = req.routeParam('postId');  // Optional
  return res.json({ id, postId });
});

// Pattern matching with regex
Route.get('/users/[0-9]+', async (req, res) => {
  const id = req.routeParam(0); // Entire matched string
  return res.json({ id });
});

Controllers

Controllers help organize your route logic into reusable classes with automatic dependency injection:

Controller Benefits

Controllers provide better organization, testability, and reusability compared to inline route handlers.
import { Controller, Request, Response } from '@orchestr-sh/orchestr';

class UserController extends Controller {
  async index(req: Request, res: Response) {
    // GET /users
    const users = await this.getUsers();
    return res.json({ users });
  }

  async show(req: Request, res: Response) {
    // GET /users/:id
    const id = req.routeParam('id');
    const user = await this.findUser(id);
    return res.json({ user });
  }

  async store(req: Request, res: Response) {
    // POST /users
    const data = req.only(['name', 'email']);
    const user = await this.createUser(data);
    return res.status(201).json({ user });
  }

  // Private methods
  private async getUsers() {
    // Database logic here
    return [];
  }

  private async findUser(id: string) {
    // Database logic here
    return { id };
  }

  private async createUser(data: any) {
    // Database logic here
    return { ...data, id: '1' };
  }
}

// Register controller routes
Route.get('/users', [UserController, 'index']);
Route.get('/users/:id', [UserController, 'show']);
Route.post('/users', [UserController, 'store']);

Request & Response

The Request and Response objects provide convenient methods for handling HTTP data:

import { Request, Response } from '@orchestr-sh/orchestr';

Route.post('/users', async (req: Request, res: Response) => {
  // Request methods
  const body = await req.json();       // Parse JSON body
  const formData = req.all();           // All request data
  const name = req.input('name');       // Single input value
  const id = req.routeParam('id');     // Route parameter
  const headers = req.headers();         // All headers
  const userAgent = req.header('User-Agent'); // Specific header
  
  // Response methods
  return res.json({ user: body });     // JSON response
  return res.status(201).json(data);  // With status code
  return res.send('Hello');             // Plain text
  return res.download('/path/to/file');   // File download
  return res.redirect('/dashboard');       // Redirect
  return res.cookie('theme', 'dark');     // Set cookie
  
  // Chaining methods
  return res
    .status(200)
    .header('X-Custom', 'value')
    .json({ success: true });
};

Middleware

Middleware allows you to filter HTTP requests entering your application:

import { Middleware, Request, Response } from '@orchestr-sh/orchestr';

const authMiddleware: Middleware = async (req: Request, res: Response, next: Function) => {
  const token = req.header('Authorization');
  
  if (!token || !isValidToken(token)) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  // Continue to next middleware
  return next();
};

// Apply middleware to specific routes
Route.get('/profile', authMiddleware, async (req, res) => {
  const user = await getCurrentUser(req);
  return res.json({ user });
});

// Apply middleware to route groups
Route.group({ middleware: [authMiddleware] }, () => {
  Route.get('/dashboard', handler1);
  Route.post('/settings', handler2);
});

Route Groups

Route groups allow you to share attributes across multiple routes:

// Group with URL prefix
Route.group({ prefix: '/api/v1' }, () => {
  Route.get('/users', handler1); // /api/v1/users
  Route.post('/posts', handler2); // /api/v1/posts
});

// Group with middleware
Route.group({ 
  prefix: '/admin',
  middleware: [authMiddleware, adminMiddleware]
}, () => {
  Route.get('/dashboard', handler1);
  Route.post('/settings', handler2);
});

// Group with multiple attributes
Route.group({
  prefix: '/api/v1',
  middleware: [corsMiddleware, rateLimitMiddleware],
  name: 'api.'
}, () => {
  Route.get('/users', handler1); // Named: api.users
  Route.post('/posts', handler2); // Named: api.posts
});

Named Routes

Named routes allow you to generate URLs for your routes:

// Define named routes
Route.get('/users', handler).setName('users.index');
Route.post('/users', handler).setName('users.store');
Route.get('/users/:id', handler).setName('users.show');

// Generate URLs
import { Route } from '@orchestr-sh/orchestr';

const userIndexUrl = Route.url('users.index');     // /users
const userShowUrl = Route.url('users.show', { id: 123 }); // /users/123
const userStoreUrl = Route.url('users.store');    // /users

// Route redirects
Route.redirect('/home', '/dashboard');           // Permanent redirect
Route.temporaryRedirect('/old-path', '/new-path'); // Temporary redirect

Best Practices

Route Organization

Group related routes together and use descriptive route names. This makes your application easier to understand and maintain.

Security Considerations

Always validate and sanitize user input in your routes. Use middleware for authentication, authorization, and input validation.

RESTful Routes

Use standard HTTP methods and naming conventions

Parameter Validation

Validate route parameters and request body data

Error Handling

Consistent error responses and status codes

Documentation

Document your API routes for developers

Testing

Write unit tests for your routes and controllers

Performance

Use caching and optimize database queries