Skip to content

RougeLi/WebServiceTemplate

Repository files navigation

Web Service Template

Node.js TypeScript Fastify Prisma PNPM

A foundational template for efficiently developing scalable web service applications.

πŸ“‹ Table of Contents


πŸ“ Project Introduction

Web Service Template is a backend service template developed based on the Fastify framework, providing developers with an efficient and scalable foundation for web service development.

Key Features

  • Efficient Development: Provides a foundational template for developing scalable web services
  • Modular Design: Separates core functionalities from business modules for easy maintenance and scalability
  • Simplified Workflow: Offers rich script commands to simplify development, testing, and deployment
  • Enhanced Development Efficiency: Supports hot reloading and automatic compilation
  • Integrated Core Functionalities: Includes built-in dependency injection, configuration management, error handling, and more

πŸš€ Quick Start

Prerequisites

  • Ensure you have the correct version of Node.js installed (see .nvmrc for details)

  • Install pnpm globally:

    npm install -g pnpm

Installation Steps

  1. Clone the repository and navigate to the project directory

    git clone <repository_url>
    cd <project_directory>
  2. Deploy the local development environment
    (See the Development Guide section for details)

  3. Copy the example environment variables file

    cp .env.example .env
  4. Install dependencies

    nvm use  # (Optional: switch to the Node.js version specified in .nvmrc)
    pnpm run install:ci
  5. Compile TypeScript files

    pnpm run build
  6. Start the development server

    pnpm start

πŸ“‚ Project Structure

This project follows a clear separation between core components and business modules:

β”œβ”€β”€ README.md
β”œβ”€β”€ index.ts                # Application entry point
β”œβ”€β”€ package.json
β”œβ”€β”€ tsconfig.json
β”œβ”€β”€ tsconfig.eslint.json
β”œβ”€β”€ eslint.config.mjs
β”œβ”€β”€ jest.config.js
β”œβ”€β”€ pnpm-lock.yaml
β”œβ”€β”€ node_modules/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ core/               # Framework core: app initialization, DI, server, config, etc.
β”‚   β”‚   β”œβ”€β”€ app/            # Application setup and initialization
β”‚   β”‚   β”œβ”€β”€ config/         # Environment and configuration management
β”‚   β”‚   β”œβ”€β”€ constants/      # Application-wide constants and enums
β”‚   β”‚   β”œβ”€β”€ di/             # Dependency injection setup
β”‚   β”‚   β”‚   β”œβ”€β”€ di-container.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ global-di-configs.ts  # Global DI configurations (e.g., Logger, Environment, Prisma, etc.)
β”‚   β”‚   β”‚   β”œβ”€β”€ on-initiate-executor.ts
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”œβ”€β”€ server/         # Web server configuration, routing, and error handling
β”‚   β”‚   β”‚   β”œβ”€β”€ bootstrap/  # Server initialization
β”‚   β”‚   β”‚   β”œβ”€β”€ modules/    # Startup modules (e.g., WebServerModule)
β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”œβ”€β”€ services/       # Core services (e.g., Logger, Environment)
β”‚   β”‚   β”œβ”€β”€ types/          # Type definitions and interfaces
β”‚   β”‚   β”‚   β”œβ”€β”€ startup-module.types.ts  # IStartupModule interface
β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”œβ”€β”€ utils/          # Utility functions and base module classes
β”‚   β”‚   └── index.ts
β”‚   β”œβ”€β”€ modules/            # Business modules (each module can be later extracted as an independent package)
β”‚   β”‚   β”œβ”€β”€ hello/          # Example Hello module
β”‚   β”‚   β”‚   β”œβ”€β”€ constants/
β”‚   β”‚   β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   β”‚   β”œβ”€β”€ dto/
β”‚   β”‚   β”‚   β”œβ”€β”€ model/
β”‚   β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”‚   β”œβ”€β”€ spec/
β”‚   β”‚   β”‚   β”œβ”€β”€ types/
β”‚   β”‚   β”‚   β”œβ”€β”€ hello.module.ts  # Module configuration & DI registration
β”‚   β”‚   β”‚   └── index.ts         # Module exports
β”‚   β”‚   └── index.ts        # Aggregated module registration
β”‚   └── index.ts            # (Optional) Additional entry point for src directory if needed
β”œβ”€β”€ test-utils/             # Testing utilities and mocks
β”‚   β”œβ”€β”€ containers/
β”‚   └── mocks/

Core Module (core)

The core directory contains the essential framework parts:

  • app/: Application initialization and server setup
  • config/: Environment loading and configuration management
  • constants/: Definitions for tokens, modes, and other constants
  • di/: Dependency injection container, including global DI configurations in global-di-configs.ts
  • server/: Fastify server setup, routing, error handling, and Swagger documentation
  • services/: Core services like Logger, Environment, etc.
  • types/: Shared TypeScript types and interfaces
  • utils/: Base classes and helper functions for modules, including the initializer-pool mechanism

Modular Startup Process

The application uses a modular startup process that allows different types of services (web server, cron jobs, message queue consumers, etc.) to be initialized, started, and stopped in a coordinated way. This is implemented through the IStartupModule interface:

export interface IStartupModule {
  readonly name: string;

  initialize(container: AppContainer): Promise<void>;

  start(): Promise<void>;

  stop(): Promise<void>;
}

Built-in Startup Modules

  • WebServerModule: Initializes and starts the Fastify web server. This module is registered by default.

Registering a Startup Module

To register a startup module:

import { setupApp } from 'src/core/app';
import { CronJobModule } from 'src/core';
import { globalDIConfigs } from 'src/core/di';
import { WebServerModule } from 'src/core/server';
import modules from 'src/modules';

(async () => {
  const app = await setupApp(globalDIConfigs, modules);

  // Register a default startup module
  app.registerStartupModule(new WebServerModule());

  await app.initialize();
  await app.start();
})();

For more details, see the Startup Modules documentation.

Initializer Pool Mechanism

The application includes an Initializer Pool utility that follows the Plugin pattern / Initialization Registry pattern. This mechanism allows for registering and executing initialization functions in a coordinated way.

// Example of using the initializer pool
import { serviceInitializerPool } from 'src/core/utils/initializer-pool';

// Register an initialization function
serviceInitializerPool.register(async () => {
  // Initialization logic here
  console.log('Initializing service...');
});

// Execute all registered initialization functions
await serviceInitializerPool.initializeAll();

Key features of the Initializer Pool:

  • Registration: Register initialization functions to be executed later
  • Sequential Execution: Execute all registered functions in sequence
  • Singleton Instance: A singleton instance (serviceInitializerPool) is available for application-wide use
  • Custom Pools: Create custom initializer pools using the createInitializerPool() function

This mechanism is particularly useful for initializing services, setting up event listeners, or performing any other startup tasks that need to be coordinated across different parts of the application.

Business Modules

All business or feature modules are organized under src/modules. Each module is self-contained and registers its own dependencies. This design improves clarity and allows modules to be later extracted as independent packages.


πŸ— System Architecture

Web Service Template adopts a modular architecture that separates core framework functionalities from business logic.

Architecture Layers

  1. Core Layer

    • Provides infrastructure: dependency injection, configuration management, server setup
    • Handles cross-cutting concerns: logging, error handling, request context
  2. Module Layer

    • Contains independent business feature modules
    • Each module is self-contained and can be developed independently
  3. Service Layer

    • Implements business logic
    • Handles data transformation and business rules
  4. Controller Layer

    • Processes HTTP requests and responses
    • Validates input and formats output
  5. Route Layer

    • Defines API endpoints
    • Connects HTTP requests to controllers

πŸ’» Development Guide

Available Script Commands

  • install:ci: Install dependencies with a frozen lockfile (ideal for CI)

    pnpm run install:ci
  • install:dev: Install dependencies without freezing the lockfile (for development)

    pnpm run install:dev
  • build: Compile TypeScript files

    pnpm run build
  • start: Run the development server with automatic recompilation

    pnpm start
  • lint: Check code quality via linting

    pnpm run lint
  • lint:fix: Automatically fix linting issues

    pnpm run lint:fix
  • unittest:coverage: Run unit tests and generate a coverage report

    pnpm run unittest:coverage

Development Workflow

  1. Live Reloading
    During development, changes are automatically compiled and the server is restarted:

    pnpm start
  2. Code Quality
    Ensures consistent code formatting and style:

    pnpm run lint:fix
  3. Testing
    Run unit tests and generate coverage reports:

    pnpm run unittest:coverage

Setting Up a Local PostgreSQL Database

  1. Install Docker

  2. Navigate to the project's .dev-app-projects directory

    cd .dev-app-projects
  3. Start the Docker containers

    docker-compose up -d
  4. Prepare the database
    Make sure your Prisma models are defined in schema.prisma before running migrations.

    pnpm run prisma:migrate

    Or generate the Prisma client:

    pnpm run prisma:generate

βš™οΈ Environment Variables

This project uses an .env file to configure environment-specific settings. Below are the main environment variables:

Variable Name Description Default Value
APP_NAME Application name for identification across services WebServiceTemplate
APP_ENV Application runtime environment development
PORT Port number where the service runs 3000

For complete configuration, please refer to the .env.example file.


πŸ”§ Module Setup Example

Business modules reside in src/modules. Here's an example using a "Hello" module:

Module Structure

src/
  β”œβ”€β”€ modules/
  β”‚   β”œβ”€β”€ hello/
  β”‚   β”‚   β”œβ”€β”€ constants/      # Module-specific constants (e.g., routes, injection tokens)
  β”‚   β”‚   β”œβ”€β”€ controllers/    # HTTP request handlers
  β”‚   β”‚   β”œβ”€β”€ dto/            # Request/response schemas
  β”‚   β”‚   β”œβ”€β”€ model/          # Data models
  β”‚   β”‚   β”œβ”€β”€ routes/         # Route definitions
  β”‚   β”‚   β”œβ”€β”€ services/       # Business logic
  β”‚   β”‚   β”œβ”€β”€ spec/           # Unit tests
  β”‚   β”‚   β”œβ”€β”€ types/          # Type definitions
  β”‚   β”‚   β”œβ”€β”€ hello.module.ts # Module configuration & DI registration
  β”‚   β”‚   └── index.ts        # Module exports
  β”‚   └── index.ts            # Aggregated module registration for the application

Module Registration

The aggregated module registration is defined in src/modules/index.ts:

// src/modules/index.ts
import { IModule } from 'src/core/types';
import { HelloModule } from './hello';

const modules: IModule[] = [
  new HelloModule(), // Register the Hello module
];

export default modules;

Example: Hello Module

The Hello module demonstrates how to register its own dependencies:

// src/modules/hello/hello.module.ts
import { BaseModule } from 'src/core/utils';
import { InjectionTokens } from './constants/injection-tokens';
import { InjectionResolverMode } from 'src/core/constants';
import { HelloRoute } from './routes/hello.route';
import { HelloController } from './controllers/hello.controller';
import { HelloService } from './services/hello.service';

export class HelloModule extends BaseModule {
  registerDependencies() {
    this.registerDependency(
      InjectionTokens.HELLO_ROUTE,
      HelloRoute,
      InjectionResolverMode.SINGLETON,
    )
      .registerDependency(
        InjectionTokens.HELLO_CONTROLLER,
        HelloController,
        InjectionResolverMode.SINGLETON,
      )
      .registerDependency(
        InjectionTokens.HELLO_SERVICE,
        HelloService,
        InjectionResolverMode.SINGLETON,
      );
  }
}

πŸ“¦ Deployment

Building the Application

  pnpm run build

Database Migration

To deploy database changes in a production environment:

  pnpm run prisma:deploy

❓ FAQ

How to add a new module?

  1. Create a new module directory under src/modules
  2. Implement necessary components (constants, controllers, services, etc.)
  3. Create a module class that extends BaseModule
  4. Register the new module in src/modules/index.ts

How to configure Swagger documentation?

Swagger documentation is automatically generated through the Fastify Swagger plugin.
You can use TypeBox in your controllers to define request and response schemas.

How to handle database migrations?

Use Prisma CLI commands to manage database migrations:

# Create a new migration
pnpm run prisma:migrate

# Deploy migrations to production environment
pnpm run prisma:deploy

# Reset database (development environment)
pnpm run prisma:reset

How to add new environment variables?

  1. Add new variables to the .env.example file
  2. Update the environment configuration schema in src/core/config
  3. Use environment variables through dependency injection

How to handle errors?

The framework provides a centralized error handling mechanism. You can:

  1. Create custom error classes
  2. Throw these errors in controllers or services
  3. The global error handler will automatically catch and format responses

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published