A modern TypeScript framework for building type-safe API endpoints with a focus on developer experience and cross-platform compatibility
@asgard/heimdall
A type-safe API endpoint framework with file-based routing and multi-runtime support.
Overview
@asgard/heimdall
is a modern TypeScript framework for building type-safe API endpoints with a focus on developer experience and cross-platform compatibility. Named after Heimdall, the Norse god who guards the rainbow bridge to Asgard, this package creates a bridge between your file structure and various cloud platforms.
Features
- 🛣️ Type-safe routing - Strong typing for paths, methods, and parameters
- 🔍 Input validation - Built-in schema validation using Standard Schema
- 💉 Service injection - Seamless integration with
@asgard/hermod
for service discovery - 🔄 Multi-runtime support - Works with Node.js, Deno, and Bun
- 📂 File-based structure - Define endpoints with a clean, intuitive file organization
- 🛡️ Security built-in - First-class support for authentication and authorization
- ☁️ Serverless ready - First-class support for AWS Lambda and API Gateway
Installation
# Using npm npm install @asgard/heimdall # Using pnpm pnpm add @asgard/heimdall # Using yarn yarn add @asgard/heimdall # Using JSR npx jsr add @asgard/heimdall
Quick Start
import { HeimdallEndpoint } from '@asgard/heimdall'; import { z } from 'zod'; import { UserService, LoggerService } from '../services'; // Define your endpoint const getUserEndpoint = new HeimdallEndpoint({ path: '/users/:id', method: 'GET', // Define parameter validation with Zod params: z.object({ id: z.string() }), // Define response schema with Zod response: z.object({ id: z.string(), name: z.string(), email: z.string().email() }), // Inject services from @asgard/hermod services: [UserService, LoggerService], // Implement the handler handler: async ({ params, services }) => { const { userService, logger } = services; logger.info(`Fetching user with id ${params.id}`); const user = await userService.findById(params.id); if (!user) { return { statusCode: 404, body: { message: `User with id ${params.id} not found` } }; } return { statusCode: 200, body: user }; } }); // Register with your Heimdall server server.registerEndpoint(getUserEndpoint);
Core Concepts
Endpoints
Endpoints are the building blocks of your API. Each endpoint combines:
- A route (path + HTTP method)
- Input validation (params, query, body)
- Response schema
- Service dependencies
- Handler implementation
Service Injection
@asgard/heimdall
integrates with @asgard/hermod
for service discovery and dependency injection:
// Define the services you need const endpoint = new HeimdallEndpoint({ // ... services: [DatabaseService, LoggerService, AuthService], handler: async ({ services }) => { // Services are available and fully typed const { databaseService, loggerService, authService } = services; // Use your services const result = await databaseService.query('...'); return { statusCode: 200, body: result }; } });
Schema Validation
@asgard/heimdall
supports multiple validation libraries through StandardSchema specification:
Using Zod
import { z } from 'zod'; const createUserEndpoint = new HeimdallEndpoint({ path: '/users', method: 'POST', // Use Zod for validation body: z.object({ name: z.string(), email: z.string().email(), age: z.number().min(18), roles: z.array(z.string()) }), // ...handler implementation });
Using Arktype
import { type } from 'arktype'; const createUserEndpoint = new HeimdallEndpoint({ path: '/users', method: 'POST', // Use Arktype for validation body: type({ name: 'string', email: 'string:email', age: 'number>=18', roles: 'string[]' }), // ...handler implementation });
@asgard/heimdall
provides first-class support for AWS Lambda and API Gateway v2:
import { HeimdallEndpoint } from '@asgard/heimdall'; import { HeimdallAWSAPIGatewayV2Handler } from '@asgard/heimdall/aws'; import { type } from 'arktype'; import { UserService } from '../services'; // Define your endpoint const getUserEndpoint = new HeimdallEndpoint({ path: '/users/:id', method: 'GET', // Use Arktype for validation params: type({ id: 'string' }), response: type({ id: 'string', name: 'string', email: 'string:email' }), services: [UserService], handler: async ({ params, services }) => { const { userService } = services; const user = await userService.findById(params.id); if (!user) { return { statusCode: 404, body: { message: 'User not found' } }; } return { statusCode: 200, body: user }; } }); // Create Lambda handler const lambdaHandler = new HeimdallAWSAPIGatewayV2Handler(getUserEndpoint); // Export the handler function for AWS Lambda export const handler = lambdaHandler.handler;
The AWS adapter:
- Automatically parses API Gateway event objects
- Validates request data against your schemas
- Injects services from your service discovery
- Handles middleware through Middy
- Returns properly formatted responses
API Reference
HeimdallEndpoint
The main class for defining endpoints.
new HeimdallEndpoint({ path: string; // The endpoint path (e.g., '/users/:id') method: HttpMethod; // 'GET', 'POST', 'PUT', 'DELETE', or 'PATCH' body?: Schema; // Request body validation schema response?: Schema; // Response body schema search?: Schema; // Query parameters schema params?: Schema; // Path parameters schema services: Service[]; // Array of service constructors handler: Function; // Handler implementation })
Best Practices
- Define precise schemas - Be specific with your validation to catch errors early
- Register services early - Set up your service discovery at application startup
- Use dependency injection - Avoid direct imports in your handlers
- File structure matters - Organize your API endpoints logically
- Handle errors consistently - Use standard error responses across endpoints
License
MIT © Lebogang Mabala
Add Package
deno add jsr:@asgard/heimdall
Import symbol
import * as heimdall from "@asgard/heimdall";
Import directly with a jsr specifier
import * as heimdall from "jsr:@asgard/heimdall";
Add Package
pnpm i jsr:@asgard/heimdall
pnpm dlx jsr add @asgard/heimdall
Import symbol
import * as heimdall from "@asgard/heimdall";
Add Package
yarn add jsr:@asgard/heimdall
yarn dlx jsr add @asgard/heimdall
Import symbol
import * as heimdall from "@asgard/heimdall";
Add Package
npx jsr add @asgard/heimdall
Import symbol
import * as heimdall from "@asgard/heimdall";
Add Package
bunx jsr add @asgard/heimdall
Import symbol
import * as heimdall from "@asgard/heimdall";