A generic web service built with NestJS and Fastify, providing dynamic CRUD operations for any model and table using reflection and metadata.
- Dynamic CRUD : Generic endpoints for any PostgreSQL table/model (currently supports PostgreSQL only)
- JWT Authentication : Endpoint protection with access and refresh tokens
- Pagination : Complete pagination support for read queries
- Automatic Mapping : Automatic property mapping via decorators
- Auto-incremented Sequences : Automatic ID generation with custom prefixes
- Route Aliases : Proxy system to create URL shortcuts
- Swagger/OpenAPI : Automatic API documentation available at the configured path (default:
/api/docs) - PostgreSQL Transactions : Transaction support for complex operations
- Node.js (v18+ recommended)
- PostgreSQL (v12+)
- npm or yarn
# Install dependencies
npm install
# Configure environment
cp .env.example .env# Application Configuration
PORT=8000 # Port on which the application will run
# Database Configuration
DB_HOST=localhost # PostgreSQL database host
DB_PORT=5432 # PostgreSQL database port
DB_NAME=your_db # PostgreSQL database name
DB_USER=your_user # PostgreSQL database username
DB_PASSWORD=your_password # PostgreSQL database password
# JWT Configuration
JWT_SECRET=your_jwt_secret # Secret key for JWT token signing (use a strong, random string)
JWT_ACCESS_TOKEN_EXPIRATION=15m # Access token expiration time (e.g., 15m, 1h, 1d)
JWT_REFRESH_TOKEN_EXPIRATION=7d # Refresh token expiration time (e.g., 7d, 30d)
# Proxy Configuration (optional)
UPSTREAM_URL=http://localhost:8000 # Upstream URL for proxying requests (used by route aliases)
# Bcrypt Configuration
BCRYPT_SALT_ROUNDS=10 # Number of salt rounds for password hashing (higher = more secure but slower)
# Swagger/OpenAPI Configuration
DOCS_TITLE=Generic Web Service Documentation # Title for the Swagger documentation page
DOCS_DESCRIPTION=A NestJS-based web service providing dynamic CRUD operations # Description for the API docs
DOCS_PATH=api/docs # Path where Swagger UI will be served
DOCS_VERSION=1.0 # Version of the API documentation
# Generic Controller Configuration
GEN_CONTROLLER_PATH=api/gen # Base path for the generic CRUD endpointsModify src/util/routeAlias.util.ts to create URL shortcuts:
export const ROUTES = {
'POST /api/gen?className=model/token.model&action=read&tableName=token': [
"/tokens",
"/list-tokens" // Good: no conflict
],
'POST /api/gen?className=model/user.model&action=read&tableName=users': [
"/users",
"/api/users/list"
]
};/fields as an alias if you have /api/gen/fields - this can cause routing conflicts and bugs.
# Development mode (with auto-reload)
npm run start:dev
# Production mode
npm run build
npm run start:prodThe server starts on http://localhost:${PORT} (default: http://localhost:8000).
- Swagger UI :
http://localhost:${PORT}/${DOCS_PATH}(default:http://localhost:8000/api/docs) - Main Endpoints :
${GEN_CONTROLLER_PATH}: Generic CRUD operations (default:/api/gen)
src/
├── annotation/ # Custom decorators
│ ├── auth.annotation.ts # JWT authentication guard
│ └── sequence.annotation.ts # @Sequence decorator for IDs
├── controller/ # API controllers
│ └── gen.controller.ts # Generic CRUD
├── interface/ # TypeScript interfaces
│ ├── pagination.interface.ts
│ └── request.interface.ts
├── middleware/ # Middlewares
│ └── alias.middleware.ts # Proxy for route aliases
├── model/ # Data models
│ ├── gen.model.ts # Generic base model
│ ├── request.model.ts # Request model
│ └── token.model.ts # Token model with JWT
├── util/ # Utilities
│ ├── bootstrap.util.ts # Startup display
│ ├── constante.util.ts # Constants
│ ├── cors.util.ts # CORS configuration
│ ├── database.util.ts # PostgreSQL connection
│ ├── docs.util.ts # Swagger configuration
│ ├── gen.util.ts # Generic utilities
│ ├── hash.util.ts # Bcrypt hashing
│ ├── network.util.ts # Network utilities
│ ├── reflect.util.ts # TypeScript reflection
│ ├── response.util.ts # Response formatting
│ ├── routeAlias.util.ts # Alias configuration
│ ├── string.util.ts # String manipulation
│ └── token.util.ts # JWT management
├── app.module.ts # Main NestJS module
└── main.ts # Application entry point
Note: All endpoint paths in the examples below use the default configuration values. The actual paths depend on your environment variables:
${GEN_CONTROLLER_PATH}for generic CRUD operations (default:/api/gen)${DOCS_PATH}for API documentation (default:/api/docs)
All /api/gen endpoints require a JWT token in the header:
Authorization: Bearer <your_token>Note: Token management is handled through the Token model using the generic CRUD endpoints. There is no dedicated token controller - use /api/gen with className=model/token.model and tableName=token.
Example - Create a token:
POST /api/gen?action=create&className=model/token.model&tableName=token
Authorization: Bearer <existing_token>
Content-Type: application/json
{
"data": {
"payload": {"userId": 123, "email": "user@example.com"},
"refreshToken": "generated_refresh_token",
"expirationDate": "2024-12-31T23:59:59Z"
}
}POST /api/gen?action=create&className=model/users.model&tableName=users
Authorization: Bearer <token>
Content-Type: application/json
{
"data": {
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"phone": "1234567890"
}
}POST /api/gen?action=read&className=model/users.model&tableName=users&page=1&limit=10
Authorization: Bearer <token>
Content-Type: application/json
{
"data": {},
"afterWhere": "created_at > '2024-01-01'"
}PUT /api/gen?action=update&className=model/users.model&tableName=users
Authorization: Bearer <token>
Content-Type: application/json
{
"objectToUpdate": {"id": "USR1"},
"objectToUpdateWith": {"email": "newemail@example.com"},
"afterWhere": "status = 'active'"
}DELETE /api/gen?action=delete&className=model/users.model&tableName=users
Authorization: Bearer <token>
Content-Type: application/json
{
"data": {"id": "USR1"},
"afterWhere": "status != 'protected'"
}// src/model/user.model.ts
import { GenModel } from './gen.model';
import { Sequence } from '../annotation/sequence.annotation';
export class User extends GenModel {
@Sequence({ name: 'user_seq', prefix: 'USR' })
id: string;
firstName: string;
lastName: string;
email: string;
phone?: string;
createdAt?: Date;
private static tableName = "users";
constructor() {
super(User.tableName);
}
}-- Create the sequence
CREATE SEQUENCE user_seq START 1;
-- Create the table
CREATE TABLE users (
id VARCHAR(20) PRIMARY KEY,
firstname VARCHAR(100) NOT NULL,
lastname VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);The model will be automatically usable via the generic API:
POST /api/gen?action=create&className=model/user.model&tableName=users
Authorization: Bearer <token>The alias system allows creating shortcuts for complex URLs:
// In routeAlias.util.ts
export const ROUTES = {
// The long URL will be accessible via /users
'POST /api/gen?className=model/user.model&action=read&tableName=users': [
"/users"
],
// Multiple aliases possible - avoid conflicts!
'PUT /api/gen?className=model/user.model&action=update&tableName=users': [
"/users/update",
"/modify-users" // Safe: no endpoint collision
],
// Token management examples
'POST /api/gen?className=model/token.model&action=create&tableName=token': [
"/create-token"
]
};The API returns standardized responses:
// Success
{
"message": "Success",
"data": {...},
"statusCode": 200
}
// Error
{
"error": "Error message",
"message": "Detailed error",
"data": null,
"statusCode": 400
}Use DatabaseUtil.withTransaction() for complex operations that require atomicity:
import { DatabaseUtil } from './util/database.util';
const result = await DatabaseUtil.getInstance().withTransaction(async (client) => {
// Create user
const user = new User();
user.firstName = 'John';
user.lastName = 'Doe';
const createdUser = await user.create(client);
// Create related token
const token = new Token();
token.userId = createdUser.id;
token.payload = { userId: createdUser.id };
const createdToken = await token.create(client);
return { user: createdUser, token: createdToken };
});Use GenModel.executeReturnedQuery() for custom SQL queries:
import { GenModel } from './model/gen.model';
const customQuery = `
SELECT u.*, t.token_value
FROM users u
LEFT JOIN tokens t ON u.id = t.user_id
WHERE u.created_at > $1
`;
const usersWithTokens = await GenModel.executeReturnedQuery(
customQuery,
new User(),
undefined, // client (optional)
['2024-01-01'] // query parameters
);Add custom validations in your models by overriding CRUD methods.
We welcome contributions! Please follow these guidelines:
- Fork the repository
- Clone your fork:
git clone https://github.com/elsy-sc/generic-ws.git - Install dependencies:
npm install - Create a feature branch:
git checkout -b feature/your-feature-name - Make your changes
- Commit your changes:
git commit -m "Add your feature" - Push to your branch:
git push origin feature/your-feature-name - Create a Pull Request
- Use TypeScript for all new code
- Follow the existing code style and naming conventions
- Update documentation as needed
- Use the GitHub issue tracker
- Provide detailed steps to reproduce
- Include environment information (Node.js version, OS, etc.)
This project is licensed under the terms specified in the LICENSE file. Currently set to "UNLICENSED" in package.json, indicating it is not open source and all rights are reserved by the author.
For commercial use or redistribution, please contact the author.
Developed with ❤️ by elsy-sc