Skip to content

mahabubx7/terry

Repository files navigation

Terry

A batteries-included Express.js framework with automatic OpenAPI documentation generation, request/response validation, API versioning, and response serialization.

License: MIT

Features

  • πŸš€ Express.js based REST API framework
  • πŸ“š Automatic OpenAPI/Swagger documentation generation
  • βœ… Request/Response validation using Zod
  • οΏ½οΏ½οΏ½ Auto-loading of route modules
  • πŸ›‘οΈ Built-in security with Helmet
  • 🌐 CORS support
  • 🎯 TypeScript support
  • πŸ”’ Built-in API versioning (v1)
  • πŸ“– Multiple API documentation UIs (Swagger, ReDoc, Scalar)
  • πŸ”₯ Hot-reload in development
  • 🏭 Production-ready build setup
  • πŸ” Response serialization and transformation
  • βš™οΈ Automatic environment configuration

Response Serialization

Terry includes automatic response serialization and transformation. Each response schema acts as a transformer:

// schema.ts
import { z } from 'zod';

export const HealthCheckResponse = z.object({
  status: z.enum(['ok', 'error']),
  uptime: z.number(),
  memory: z.object({
    used: z.number(),
    total: z.number(),
  }),
}).openapi('HealthCheckResponse');

// routes.ts
{
  method: 'get',
  path: '/',
  schema: {
    response: HealthCheckResponse,
  },
  handler: async (req, res) => {
    // This will be validated against HealthCheckResponse schema
    return {
      status: 'ok',           // Must be 'ok' or 'error'
      uptime: process.uptime(),
      memory: {
        used: 100,
        total: 1000,
      }
    };
  }
}

// Invalid responses will throw 422 Unprocessable Entity
return {
  status: 'unknown',  // Error: Invalid enum value
  uptime: 'invalid'   // Error: Expected number, received string
};

Environment Configuration

Terry automatically loads environment variables from .env file and validates them using Zod:

// config/env.ts
import { z } from 'zod';

const envSchema = z.object({
  PORT: z.string().transform(Number).default('3456'),
  NODE_ENV: z.enum(['development', 'production', 'test']),
  API_PREFIX: z.string().default('/api'),
  // ... other validations
});

// Usage in your code
import env from '../config/env';
app.listen(env.PORT);

Create a .env file based on .env.example:

# Server
PORT=3456
NODE_ENV=development

# API
API_PREFIX=/api

# Documentation
DOCS_ENABLED=true

# Security
CORS_ORIGIN=*
RATE_LIMIT_WINDOW=15
RATE_LIMIT_MAX=100

# Logging
LOG_LEVEL=debug
PRETTY_LOGGING=true

Project Structure

src/
β”œβ”€β”€ app/                    # Application modules
β”‚   β”œβ”€β”€ users/             # User module example
β”‚   β”‚   β”œβ”€β”€ users.routes.ts # Route definitions
β”‚   β”‚   └── users.schema.ts # Zod schemas
β”‚   β”œβ”€β”€ todos/             # Todo module example
β”‚   β”‚   β”œβ”€β”€ todos.routes.ts
β”‚   β”‚   └── todos.schema.ts
β”‚   └── health/            # Health check module
β”‚       β”œβ”€β”€ health.routes.ts
β”‚       └── health.schema.ts
β”œβ”€β”€ lib/                   # Framework core
β”‚   β”œβ”€β”€ openapi.ts        # OpenAPI configuration
β”‚   └── router.ts         # Route builder
β”œβ”€β”€ config/               # Configuration
β”‚   β”œβ”€β”€ env.ts           # Environment variables
β”‚   └── logger.ts        # Logging configuration
└── main.ts              # Application entry point

Module Structure

Each module should follow this structure:

  1. *.routes.ts - Route definitions with handlers
import { ModuleRoutes } from '../../lib/openapi';
import { MySchema } from './my.schema';

const routes: ModuleRoutes = [
  {
    method: 'get',
    path: '/',
    schema: {
      response: MySchema,
    },
    handler: async (req, res) => {
      // Your handler logic
      return { data: 'example' };
    },
    summary: 'List items',
    description: 'Get a list of items',
    tags: ['MyModule']
  }
];

module.exports = routes;
module.exports.default = routes;
  1. *.schema.ts - Zod schemas for validation
import { z } from 'zod';

export const MySchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
}).openapi('MySchema');

Environment Modes

Development Mode

  • Uses TypeScript files directly
  • Hot-reload enabled
  • Detailed error logging
  • Pretty-printed logs
  • Source maps enabled
npm run dev

Production Mode

  • Uses compiled JavaScript
  • Optimized for performance
  • Minimal error logging
  • JSON formatted logs
  • No source maps
npm run build
npm run start:prod

API Documentation

The framework automatically generates OpenAPI documentation from your route definitions and schemas. Access the documentation at:

  • Swagger UI: http://localhost:3456/api/docs/swagger
  • ReDoc: http://localhost:3456/api/docs/redoc
  • Scalar: http://localhost:3456/api/docs/scalar
  • OpenAPI JSON: http://localhost:3456/api/docs/api.json

Docker Support

Production

docker compose build api
docker compose up api

Development with Hot-Reload

docker compose --profile dev up api-dev

Environment Variables

Create a .env file in the root directory:

# Server
PORT=3456
NODE_ENV=development

# API
API_PREFIX=/api

# Documentation
DOCS_ENABLED=true

# Security
CORS_ORIGIN=*
RATE_LIMIT_WINDOW=15
RATE_LIMIT_MAX=100

# Logging
LOG_LEVEL=debug
PRETTY_LOGGING=true

Scripts

  • npm run dev: Start development server with hot-reload
  • npm run build: Build for production
  • npm run start:prod: Start production server
  • npm run lint: Run linter
  • npm run format: Format code
  • npm run clean: Clean build directory

Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create a new Pull Request

License

Terry is open-source software licensed under the MIT license.

Copyright (c) 2024 Cursor Inc.

About

Terry => Typescript + Express.js + Openapi-TS based boilerplate/custom-framework

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published