π₯¦ Simple but Awesome TypeScript DI Framework for Node.js π₯¦
What is Tarpit?β
Tarpit is a modern Dependency Injection (DI) Framework built specifically for TypeScript and Node.js applications. It provides a powerful platform for building reusable, testable, and maintainable server-side applications with a clean, decorator-based architecture.
Key Featuresβ
- π― Type-Safe DI: Leverages TypeScript's type system for dependency resolution
- π Decorator-Based: Clean, declarative syntax using TypeScript decorators
- π¦ Modular Architecture: Built-in support for modules and component organization
- π§ Extensible: Easy to extend with custom providers and modules
- β‘ Lightweight: Minimal overhead with focused functionality
- π§ͺ Testing-Friendly: Built with testability in mind
Core Conceptsβ
Tarpit's Dependency Injection system is built around three fundamental concepts:
Platform - The application container that manages the entire dependency injection system. It orchestrates module imports, controls the application lifecycle, and serves as the central registry for all services and providers.
Providers - The recipes that tell the DI system how to create and supply dependencies. Providers define how services, values, and factories are registered and resolved, whether through class constructors, factory functions, or pre-existing values.
Injector - The core engine that resolves dependencies by matching injection tokens to providers. It maintains a hierarchical chain of dependency lookups and acts as the runtime dependency resolution mechanism.
These three concepts work together: the Platform registers Providers with the Injector, which then resolves dependencies when services are requested.
Quick Startβ
Prerequisitesβ
Before getting started, ensure you have:
- Node.js (v14.0.0 or higher)
- TypeScript (v4.0 or higher)
- npm or yarn package manager
You can find complete working examples in the example/
directory of this repository, organized by module.
Installationβ
Create a new project directory:
mkdir my-tarpit-app
cd my-tarpit-app
Initialize your project:
npm init -y
Install TypeScript and development dependencies:
# Install TypeScript globally or as dev dependency
npm install -D typescript ts-node @types/node
# Initialize TypeScript configuration
npx tsc --init
Configure TypeScript for decorators in tsconfig.json
:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Install Tarpit HTTP module (includes core dependencies):
npm install @tarpit/http @tarpit/judge @tarpit/config reflect-metadata
reflect-metadata
is required for TypeScript decorator metadata reflection. Tarpit's dependency injection system uses this to automatically detect constructor parameter types and enable type-safe dependency resolution.
Hello World Exampleβ
Create src/index.ts
:
import { load_config } from '@tarpit/config'
import { Platform, TpConfigSchema } from '@tarpit/core'
import { HttpServerModule, TpRouter, Get, PathArgs } from '@tarpit/http'
import { Jtl } from '@tarpit/judge'
@TpRouter('/')
class HelloRouter {
@Get('hello')
async say_hello() {
return { message: 'Hello, Tarpit!' }
}
@Get('user/:id')
async get_user(args: PathArgs<{ id: string }>) {
const user_id = args.ensure('id', Jtl.string)
return { user_id, name: `User ${user_id}` }
}
}
const config = load_config<TpConfigSchema>({
http: { port: 4100 }
})
const platform = new Platform(config)
.import(HttpServerModule)
.import(HelloRouter)
.start()
Run your application:
npx ts-node src/index.ts
Test your endpoints:
# Basic hello endpoint
curl http://localhost:4100/hello
# Parameterized endpoint
curl http://localhost:4100/user/123
This example demonstrates:
- Basic Routing: Using
@TpRouter
to define route prefixes - HTTP Methods: Using
@Get
decorator for GET endpoints - Path Parameters: Extracting and validating URL parameters
- JSON Responses: Returning structured data from endpoints
Service Injection Exampleβ
For more complex applications, you'll want to organize your code using dependency injection. Here's how to create injectable services with HTTP routing:
import { load_config } from '@tarpit/config'
import { Platform, TpConfigSchema, TpService } from '@tarpit/core'
import { HttpServerModule, TpRouter, Get, PathArgs } from '@tarpit/http'
import { Jtl } from '@tarpit/judge'
// 1. Declaration - Mark classes as injectable services
@TpService()
class DatabaseService {
connect() {
console.log('Connected to database')
}
query(sql: string) {
console.log(`Executing query: ${sql}`)
return []
}
find_user(id: string) {
console.log(`Finding user with ID: ${id}`)
return { id, name: `User ${id}`, email: `user${id}@example.com` }
}
}
@TpService()
class UserService {
// 2. Dependency will be injected automatically
constructor(private db: DatabaseService) {}
create_user(name: string) {
this.db.connect()
const result = this.db.query(`INSERT INTO users (name) VALUES ('${name}')`)
console.log(`Created user: ${name}`)
return { id: Date.now(), name }
}
get_user(id: string) {
this.db.connect()
return this.db.find_user(id)
}
}
// 3. HTTP Router using injected services
@TpRouter('/api/users')
class UserRouter {
// 4. Service injection in router
constructor(private userService: UserService) {}
@Get('')
async list_users() {
return {
message: 'User list endpoint',
users: ['Alice', 'Bob', 'Charlie']
}
}
@Get(':id')
async get_user(args: PathArgs<{ id: string }>) {
const user_id = args.ensure('id', Jtl.string)
const user = this.userService.get_user(user_id)
return user
}
@Get('hello/:name')
async greet_user(args: PathArgs<{ name: string }>) {
const name = args.ensure('name', Jtl.string)
this.userService.create_user(name)
return {
message: `Hello, ${name}!`,
user: { name, created: true }
}
}
}
async function main() {
// 5. Registration - Register services and router with platform
const config = load_config<TpConfigSchema>({
http: { port: 4100 }
})
const platform = new Platform(config)
.import(HttpServerModule)
.import(DatabaseService)
.import(UserService)
.import(UserRouter)
await platform.start()
console.log('Server started on http://localhost:4100')
console.log('Try these endpoints:')
console.log(' GET http://localhost:4100/api/users')
console.log(' GET http://localhost:4100/api/users/123')
console.log(' GET http://localhost:4100/api/users/hello/Alice')
}
main().catch(console.error)
Run the example:
npx ts-node example/basic/service-injection.ts
Test the API endpoints:
# List all users
curl http://localhost:4100/api/users
# Get a specific user
curl http://localhost:4100/api/users/123
# Create and greet a user
curl http://localhost:4100/api/users/hello/Alice
This example demonstrates:
- Service Declaration: Using
@TpService()
to mark classes as injectable - Dependency Injection: Automatic injection of dependencies through constructor parameters
- Router Injection: Injecting services into HTTP routers for API endpoints
- Service Registration: Importing services and routers into the platform
- HTTP Integration: Combining dependency injection with REST API endpoints
- Path Parameters: Extracting and validating URL parameters with type safety
Next Stepsβ
Ready to dive deeper? Explore our comprehensive documentation:
Core Frameworkβ
- Core Concepts - Learn about dependency injection, providers, and the platform
- Platform Lifecycle - Understanding application startup and shutdown
- Dependency Injection - Advanced DI patterns and best practices
HTTP Server Moduleβ
- HTTP Server - Web APIs, routing, middleware, and authentication
- Request Handling - Processing HTTP requests and responses
Other Modulesβ
- RabbitMQ Module - Message queuing and event-driven architecture
- Schedule Module - Cron jobs and background tasks
- Content Type Module - Working with different data formats
For hands-on learning, check out the example/
directory with runnable code samples for each module.