Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ZyroHub - Router Module

This module provides routing capabilities, allowing for easy management of application routes and middleware.

Table of Contents

Getting Started

To install the router module, use one of the following package managers:

NPM Repository

# npm
npm install @zyrohub/module-router
# yarn
yarn add @zyrohub/module-router
# pnpm
pnpm add @zyrohub/module-router
# bun
bun add @zyrohub/module-router

Using Module and Registering Controllers

To use the router module, you need to install @zyrohub/core and register the RouterModule:

const core = new Core({
	modules: [
		RouterModule.mount({
			// using manual controller registration
			controllers: [HomeController, AuthController, StoreController],

			// or using automatic controller registration by specifying the path
			loader: {
				path: `${__dirname}/controllers`,
				// optional glob pattern to match controller files
				pattern: /\.controller\.ts$/
			}
		})
	],
	providers: [
		// register any services or repositories here for dependency injection
		// see @zyrohub/core documentation for more details
	]
});

core.init();

TypeScript Configuration

⚠️ Important: To use the Dependency Injection system, you must enable emitDecoratorMetadata in your tsconfig.json.

{
	"compilerOptions": {
		"experimentalDecorators": true,
		"emitDecoratorMetadata": true
	}
}

Creating a Controller

You can create a controller by using the @Controller decorator and defining routes with decorators like @Get, @Post, @Put, etc.

import { Controller, Get, HttpResponse } from '@zyrohub/module-router';

import { ProductsRepository } from './repositories/ProductsRepository.js';
import { StoreProductsSchema } from './schemas/StoreProductsSchema.js';

@Controller({
	path: '/'
})
class StoreController {
	// Dependency injection can be used in the constructor
	constructor(
		// inject services or other dependencies here
		private readonly productsRepository: ProductsRepository
	) {}

	@Get('/products')
	getProducts(context: StoreProductsSchema) {
		return HttpResponse.success({ products: this.productsRepository.findAll() });
	}
}

Route Schema

const LoginSchema = new RouteSchema({
	body: {}, // see supported validation below
	query: {}, // see supported validation below
	params: {}, // see supported validation below

	consumes: [], // filter accepted content types (default: ["application/json"])

	files: { // see files configuration below
		fields: [],
		options: {}
	},

    // Optional metadata for documentation purposes
    meta: {
        tags: ['auth', 'login'],
        summary: 'User login endpoint',
        description: 'Endpoint for user login that validates input data',
    }
});

Using the above schema, you can define the route schema and type the context parameter in your handler to ensure type safety and validation.

import { Controller, Post, HttpResponse } from '@zyrohub/module-router';

import { LoginSchema } from './schemas/LoginSchema.js';

@Controller({
	path: '/auth'
})
class AuthController {
	@Post('/login', LoginSchema)
	login(context: typeof LoginSchema.context) {
		const body = context.request.body; // Access typed and validated body
		const query = context.request.query; // Access typed and validated query
		const params = context.request.params; // Access typed and validated params

		const files = context.files; // Access typed and validated files data

		// Your login logic here

		return HttpResponse.success({ token: '<generated_session_token>' });
	}
}

Automatic Validation

You can use zod, yup or class-validator for automatic validation of request data.

Using Zod

import { z } from 'zod';

const LoginSchema = new RouteSchema({
	body: z.object({
		username: z.string().min(3),
		password: z.string().min(6)
	}),
	query: z.object({
		rememberMe: z.boolean().optional()
	}),
	params: z.object({
		userId: z.string().optional()
	})
});

Using Yup

import * as yup from 'yup';

const LoginSchema = new RouteSchema({
	body: yup.object().shape({
		username: yup.string().min(3).required(),
		password: yup.string().min(6).required()
	}),
	query: yup.object().shape({
		rememberMe: yup.boolean().notRequired()
	}),
	params: yup.object().shape({
		userId: yup.string().notRequired()
	})
});

Using Class-Validator

import { IsString, MinLength, IsBoolean, IsOptional } from 'class-validator';

class LoginBody {
	@IsString()
	@MinLength(3)
	username: string;

	@IsString()
	@MinLength(6)
	password: string;
}

class LoginQuery {
	@IsBoolean()
	@IsOptional()
	rememberMe?: boolean;
}

class LoginParams {
	@IsString()
	@IsOptional()
	userId?: string;
}

const LoginSchema = new RouteSchema({
	body: LoginBody,
	query: LoginQuery,
	params: LoginParams
});

Receiving Files

const UpdateProfileSchema = new RouteSchema({
	body: UpdateProfileBody,

	consumes: ['multipart/form-data'], // if no value is defined, the consumer will be defined automatically when "files" is defined

	files: {
		fields: [
			{ // file specific options
				name: 'avatar',
				maxSize: 20 * 1024 * 1024, // 20 MB (single limit per file)
				minCount: 1,
				maxCount: 1,
				mimeTypes: ['image/png']
			}
		],
		options: { // general options for all files
			maxFiles: 1, // total max files
			maxFileSize: 10 * 1024 * 1024, // 10 MB (limit per file)
			mimeTypes: ['image/png'] // accepted mime types
			any: false // if you want to receive undocumented files in "fields"
		}
	}
});

HttpResponse

The HttpResponse class provides static methods to create standardized HTTP responses.

import { HttpResponse } from '@zyrohub/module-router';

// Successful response with data
const successResponse = HttpResponse.success({ userId: 1, username: 'john_doe' });
// { success: true, status: 200, data: { userId: 1, username: 'john_doe' } }

const createdResponse = HttpResponse.created({ resourceId: 123 });
// { success: true, status: 201, data: { resourceId: 123 } }

// Error response with message
const errorResponse = HttpResponse.error(401, 'UNAUTHORIZED', { message: 'Invalid credentials' });
// { success: false, status: 401, code: 'UNAUTHORIZED', data: { message: 'Invalid credentials' } }

// Error response with text status (e.g., 'BAD_REQUEST')
const errorResponseWithTextStatus = HttpResponse.error('BAD_REQUEST', 'INVALID_INPUT', {
	message: 'Input data is invalid'
});
// { success: false, status: 400, code: 'INVALID_INPUT', data: { message: 'Input data is invalid' } }

Creating Middleware

You can create middleware by extending the RouterMiddleware class, using the @Middleware() decorator, and implementing the execute method.

import { Middleware, RouterMiddleware, RouterMiddlewareContext, HttpResponse } from '@zyrohub/module-router';

export interface AuthMiddlewareOptions {
	secretKey: string;
}

@Middleware()
export class AuthMiddleware extends RouterMiddleware {
	// Your can add optional options to the middleware
	static options: AuthMiddlewareOptions;

	// Dependency injection can be used in the constructor
	constructor(
		// inject services or other dependencies here
		private readonly authService: AuthService
	) {
		super();
	}

	async execute(context: RouterMiddlewareContext, options: AuthMiddlewareOptions) {
		const authHeader = context.request.headers['authorization'];

		if (!authHeader || !this.authService.verifyToken(authHeader, options.secretKey)) {
			return HttpResponse.error(401, 'INVALID_TOKEN', { message: 'Invalid or missing authorization token' });
		}

		context.state.user = await this.authService.getUserFromToken(authHeader); // You can store user info in context state for later use in route handlers

		// automatically proceed to the next middleware or route handler
	}
}

Using Middleware in Controllers or Routes

import { Controller, HttpResponse, Post, UseMiddleware } from '@zyrohub/module-router';

import { AnotherMiddleware } from './middlewares/AnotherMiddleware.js';
import { AuthMiddleware } from './middlewares/AuthMiddleware.js';
import { BuyItemSchema } from './schemas/BuyItemSchema.js';

@Controller({
	path: '/',
	// applying middlewares to all routes in the controller
	middlewares: [AnotherMiddleware]
})
class StoreController {
	@Post('/buy')
	// normal usage of middleware
	@UseMiddleware(AuthMiddleware)
	// usage of middleware with options
	@UseMiddleware(AuthMiddleware.configure({ secretKey: 'my_secret_key' }))
	// multiple middlewares
	@UseMiddleware(AnotherMiddleware, AuthMiddleware.configure({ secretKey: 'my_secret_key' }))
	buyItem(context: BuyItemSchema) {
		const { itemId, quantity } = context.request.body;

		// Your business logic for buying an item

		return HttpResponse.success({ itemId, quantity });
	}
}

Getting generated routes

You can access the registered controllers and their routes from other modules or services by using the core storage or by accessing the RouterModule instance.

import { BaseModule, Core, Module } from '@zyrohub/core';
import { DefinedController, ROUTER_CONTROLLERS_STORAGE_KEY } from '@zyrohub/module-router';

@Module()
class AnotherModule extends BaseModule {
	async init(data: { core: Core }) {
		// from core storage
		const controllers: DefinedController[] = data.core.storage.get(ROUTER_CONTROLLERS_STORAGE_KEY);
		console.log(controllers);

		// using the router module
		const routerModule = data.core.getModuleOrThrow(RouterModule);
		console.log(routerModule.controllers);
	}
}

Declaring Request and Response types

You can declare your custom request and response types by using TypeScript module augmentation. This is useful when integrating with frameworks like Express or Fastify.

// e.g., src/types/router.d.ts
import '@zyrohub/module-router';

declare module '@zyrohub/module-router' {
	interface RouterGlobalInputs {
		request: YourCustomRequestType; // e.g., Express.Request or FastifyRequest
		response: YourCustomResponseType; // e.g., Express.Response or FastifyReply

		state: YourCustomStateType; // Optional custom state type for context data (e.g., for storing user info after authentication)
	}
}

About

This module provides routing capabilities, allowing for easy management of application routes and middleware.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages