@filipgorny/service-api
v0.0.3
Published
A library for defining and creating HTTP service APIs with validation and documentation
Downloads
11
Maintainers
Readme
@filipgorny/service-api
A TypeScript library for defining protocol-agnostic service APIs with automatic validation, type safety, and flexible strategies.
Features
- ✅ Protocol-agnostic architecture - Abstract
Apiclass allows REST, GraphQL, gRPC implementations - ✅ REST API ready -
RestApiimplementation with Express strategy - ✅ Strategy pattern - Easily switch between different protocols and transports
- ✅ Type-safe - Full TypeScript support with automatic type extraction via reflect-metadata
- ✅ Automatic validation - Using class-validator decorators
- ✅ Built-in endpoints -
/healthand/Documentationavailable by default - ✅ Built-in CORS - Enabled by default for all REST routes
- ✅ URL params support - Automatic handling of route parameters (e.g.,
/book/:id) - ✅ Error handling - Automatic try-catch with proper HTTP error responses
- ✅ Zero config - Services work from the start with sensible defaults
Installation
npm add @filipgorny/service-api
# or
pnpm add @filipgorny/service-apiQuick Start
1. Configure TypeScript
Add to your tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}2. Create a REST API Service
import { RestApi, express } from "@filipgorny/service-api";
// Create REST API with Express strategy
const api = new RestApi(express(3000));
// Define a simple endpoint
api.get("hello", async () => {
return { message: "Hello World!" };
});
// Start the server
api.run();That's it! Your service is now running with:
GET /hello- Your custom endpointGET /health- Health check with version and uptimeGET /Documentation- API documentation
3. Full CRUD Example
import { RestApi, express } from "@filipgorny/service-api";
class CreateBookInput {
title: string;
author: string;
}
class Book {
id: number;
title: string;
author: string;
available: boolean;
}
// Mock database
const books: Book[] = [];
let nextId = 1;
const api = new RestApi(express(3000));
// GET /books - List all books
api.get("books", async () => {
return { books };
});
// GET /book/:id - Get book by ID
api.get("book/:id", async (input: { id: number }) => {
const book = books.find((b) => b.id === input.id);
if (!book) throw new Error("Book not found");
return book;
});
// POST /book - Create a new book
api.create("book", async (input: CreateBookInput) => {
const book = {
id: nextId++,
title: input.title,
author: input.author,
available: true,
};
books.push(book);
return book;
});
// PUT /book/:id - Update a book
api.update("book/:id", async (input: Partial<Book> & { id: number }) => {
const book = books.find((b) => b.id === input.id);
if (!book) throw new Error("Book not found");
Object.assign(book, input);
return book;
});
// DELETE /book/:id - Delete a book
api.delete("book/:id", async (input: { id: number }) => {
const index = books.findIndex((b) => b.id === input.id);
if (index === -1) throw new Error("Book not found");
books.splice(index, 1);
});
api.run();API Architecture
Base Api Class (Abstract)
The abstract Api class is protocol-agnostic and can be extended for different API types:
import { Api } from "@filipgorny/service-api";
// Future implementations could include:
// - GraphQLApi extends Api
// - GrpcApi extends Api
// - WebSocketApi extends ApiRestApi Class
REST-specific implementation with HTTP methods:
class RestApi extends Api {
get<TInput, TOutput>(options, handler): this;
create<TInput, TOutput>(options, handler): this; // Maps to POST
update<TInput, TOutput>(options, handler): this; // Maps to PUT
delete<TInput>(options, handler): this; // Maps to DELETE
}Method Options
You can pass either a string (route name) or an object with more details:
// Simple string
api.get("users", handler);
// With description
api.get({ name: "users", description: "Get all users" }, handler);REST API Methods
All methods accept route name/options and a handler function:
get<TInput, TOutput>(options, handler)- GET endpoint for reading datacreate<TInput, TOutput>(options, handler)- POST endpoint for creating resourcesupdate<TInput, TOutput>(options, handler)- PUT endpoint for updating resourcesdelete<TInput>(options, handler)- DELETE endpoint for deleting resources
Built-in Default Endpoints
Every REST API automatically includes:
GET /health
Health check endpoint for monitoring:
{
"status": "healthy",
"version": "0.0.3",
"timestamp": "2025-11-09T12:00:00.000Z",
"uptime": 123.456
}GET /Documentation
API documentation endpoint (returns HTML when using OpenAPI view).
URL Parameters
The Express strategy automatically handles URL parameters:
api.get("book/:id", async (input: { id: number }) => {
// input.id is automatically parsed from URL
// Numeric strings are converted to numbers
return books.find((b) => b.id === input.id);
});
// Also works with multiple params
api.get(
"author/:authorId/book/:bookId",
async (input: { authorId: number; bookId: number }) => {
// Both params available in input
},
);Input Handling
The library merges URL params, query params, and request body:
- GET requests:
req.query+req.params - POST/PUT/DELETE requests:
req.body+req.params - URL params take precedence over body/query
// GET /book/123?format=json
api.get("book/:id", async (input: { id: number; format?: string }) => {
// input = { id: 123, format: 'json' }
});Error Handling
All endpoint handlers are automatically wrapped in try-catch:
- Handler errors →
500 Internal Server Error
{
"error": "Internal service error",
"message": "Book not found"
}Errors are automatically logged (suppressed in test environments).
Strategies
Express Strategy
The default REST strategy using Express.js:
import { express } from "@filipgorny/service-api";
const api = new RestApi(express(3000));Features:
- Built-in CORS support
- JSON body parsing
- Automatic route registration
- URL parameter parsing with type conversion
Future Strategies
The architecture supports future strategies:
- GraphQL strategy
- gRPC strategy
- WebSocket strategy
- Custom strategies implementing the
Strategyinterface
Advanced Usage
Disable Default Methods
const api = new RestApi(express(3000), false); // No /health or /DocumentationAccess Strategy Internals
const api = new RestApi(express(3000));
// Configure routes
api.get("users", handler);
// Access Express app (for testing or custom middleware)
const strategy = (api as any).strategy;
const app = strategy.app;Testing
The library includes comprehensive tests demonstrating all features. See:
tests/api.test.ts- Basic API teststests/rest-api-full.test.ts- Complete CRUD workflow tests
Dependencies
express- HTTP server (for REST)cors- CORS middlewareclass-validator- Validation decoratorsclass-transformer- DTO transformationreflect-metadata- Runtime type information@filipgorny/logger- Logging support
Architecture Benefits
Protocol-Agnostic Design
The separation between Api (abstract) and RestApi (concrete) allows:
- Multiple protocol implementations - Add GraphQL, gRPC without changing core
- Consistent API - Same patterns across different protocols
- Easy migration - Switch protocols with minimal code changes
- Better testing - Mock strategies for unit tests
Example: Future GraphQL Implementation
// Future possibility
class GraphQLApi extends Api {
query<TInput, TOutput>(options, handler): this
mutation<TInput, TOutput>(options, handler): this
subscription<TInput, TOutput>(options, handler): this
protected defaultMethods = [...] // GraphQL-specific defaults
}License
MIT
