Projectsmahi

Mahi

Framework

A TypeScript application framework for building APIs and services on Node.js — container, ORM, router, queues, mail and more.

Mahi

A TypeScript application framework for building APIs and services on Node.js.

Mahi takes the architecture that makes Laravel productive, service providers with a two-stage lifecycle, a service container, driver-based managers, an expressive ORM, first-class queues and scheduling, and rebuilds it for TypeScript, where the type system does work that PHP's runtime magic had to do at runtime.

export class PostsServiceProvider extends ServiceProvider {
  routes(router: Router): void {
    router.get("/posts/{post}", ShowPostController).name("posts.show");
  }
}

export class ShowPostController extends Controller {
  async handle(request: Request) {
    const post = await request.model(Post);
    await post.load("author", "comments");

    return HttpResponse.json(await new PostResource(post).toJson());
  }
}

Getting started

npm create mahi@latest my-app
cd my-app
./artisan serve

See the installation guide for the full walkthrough.

Design principles

These are the decisions that shape everything else, and the places Mahi deliberately diverges from Laravel.

Explicit resolution, no decorators. There is no reflect-metadata, no @Injectable, no constructor auto-wiring. A service provider binds a factory; consumers call app.make(TOKEN). Resolution is a function call you can read and follow, and the container never has to guess what a constructor parameter means.

Types are the documentation. data_get(user, "profile.city") is checked against user's shape at compile time and returns the type at that path. request.validated() returns a type derived from the rules you declared. A model's with("author") narrows the result type to include the loaded relation. Where Laravel returns mixed and asks you to know, Mahi returns a type.

No dynamic facade proxies. Laravel's facades forward arbitrary method names at runtime, which no type checker can follow. Mahi's facades are hand-written classes with real static methods that each proxy exactly one token, so renaming an underlying method is a compile error, not a runtime surprise.

Synchronous driver resolution. manager.driver() never returns a promise. Constructing a driver handle is cheap; real I/O is lazy. Drivers that genuinely need async setup implement Connectable and are connected by their owning provider's boot().

Escape hatches are first-class. Every abstraction exposes the layer below it: builder.toBase() for the query builder, .raw() for the underlying Kysely query, Expression.raw() for literal SQL, request.raw() for the Hono context. You should never have to fight the framework to do something it didn't anticipate.

Documentation

Getting started

Core concepts

The HTTP layer

  • Routing: routes, groups, middleware, named routes, URLs
  • Requests: input, files, form requests
  • Validation: rules, custom messages, typed output
  • Controllers: single-action controllers
  • Responses: JSON, files, redirects, API resources

Database

Security

Infrastructure

Tooling

Packages

Mahi is a set of packages, not a monolith. Install what you use.

PackageContents
@mahiframework/coreContainer, Application, ServiceProvider, Config, Env, Logger, Str/Arr/Collection, helpers
@mahiframework/httpHTTP kernel (Hono), router, request, responses, resources, middleware
@mahiframework/databaseModels, query builder, relations, migrations, factories, seeders
@mahiframework/validationRule, Validator, ValidationException
@mahiframework/authGuards (token, session), user providers, password reset, verification
@mahiframework/authorizationGates, policies, abilities
@mahiframework/cacheCache stores, locks, rate limiter
@mahiframework/queueJobs, queue drivers, workers, middleware
@mahiframework/scheduleRecurring task scheduling
@mahiframework/healthReadiness checks, GET /health, ./artisan health
@mahiframework/eventsEvent dispatcher, listeners
@mahiframework/broadcastingWebsocket broadcasting
@mahiframework/storageFilesystem disks
@mahiframework/mailMailables, SMTP/log transports
@mahiframework/notificationsMulti-channel notifications
@mahiframework/encryptionEncrypter, hasher, signer
@mahiframework/redisRedis-backed cache/queue/broadcast drivers
@mahiframework/cliConsole kernel, make:* generators, migration commands
@mahiframework/testingTest application, HTTP client, database assertions
@mahiframework/datetimeImmutable date/time library
@mahiframework/snowflakeDistributed 63-bit IDs
@mahiframework/tuiTerminal UI: prompts, tables, spinners, progress bars
@mahiframework/pipelineSend a value through a series of pipes
@mahiframework/processRun external commands
@mahiframework/http-clientOutbound HTTP: fluent requests, retries, Http.fake()
@mahiframework/facadesThe Facade<T> mixin

Requirements

  • Node.js 22 or later
  • No PHP, no compiled extensions beyond better-sqlite3 and argon2