Motoko Showcase: Liminal - An HTTP framework

I recently completed a Dfinity grant focused on improving the Motoko ecosystem by providing better HTTP support for developers. The goal was to create a framework so that not everyone has to write HTTP handling from scratch when building web applications on the Internet Computer. I’m looking forward to any and all feedback from the community.

Project highlights

Liminal is a middleware-based HTTP framework designed for building web applications and APIs with Motoko on the Internet Computer. It targets Motoko developers who want a solid foundation for their web services without having to build HTTP handling from scratch.

The framework addresses the need for common web development features on IC, including middleware composition, routing, authentication, content negotiation, file uploads, and integration with IC-specific features like asset canisters. Liminal brings familiar web development patterns to the IC ecosystem while working with the unique query/update call model.

Features

  • :counterclockwise_arrows_button: Middleware Pipeline: Compose your application using reusable middleware components - create custom middleware or use built-in ones, with automatic query/update flow handling
  • :motorway: Advanced Routing: Route matching with parameter extraction (/users/{id}), wildcards (*, **), and nested route groups
  • :locked: Security: Built-in CORS, CSP (Content Security Policy), CSRF protection, and rate limiting
  • :locked_with_key: Authentication: JWT parsing/validation and OAuth 2.0 with PKCE support (Google, GitHub, custom providers)
  • :package: Asset Integration: Interface with Internet Computer’s certified asset canisters
  • :rocket: Performance: Automatic response compression (gzip) and content negotiation
  • :outbox_tray: File Uploads: Parse multipart/form-data for handling file uploads (up to 2MB)
  • :shuffle_tracks_button: Content Negotiation: Automatically convert Candid data to JSON, CBOR, XML based on Accept headers
  • :memo: Session Management: Configurable session storage with cookie handling
  • :shield: Rate Limiting: Protect APIs from abuse with flexible rate limiting strategies
  • :bar_chart: Logging: Built-in logging system with configurable levels and scoped loggers

How to install

Liminal uses the MOPS package manager. First, set up MOPS following the instructions from MOPS Site, then add Liminal to your project:

mops add liminal

Usage Example

Here’s a minimal example to get started:

import Liminal "mo:liminal";
import Route "mo:liminal/Route";
import Router "mo:liminal/Router";
import RouteContext "mo:liminal/RouteContext";
import RouterMiddleware "mo:liminal/Middleware/Router";
import CORSMiddleware "mo:liminal/Middleware/CORS";

actor {
    // Define your routes
    let routerConfig = {
        prefix = ?"/api";
        identityRequirement = null;
        routes = [
            Router.getQuery(
                "/hello/{name}",
                func(context : RouteContext.RouteContext) : Route.HttpResponse {
                    let name = context.getRouteParam("name");
                    context.buildResponse(#ok, #text("Hello, " # name # "!"));
                }
            )
        ]
    };

    // Create the HTTP App with middleware
    let app = Liminal.App({
        middleware = [
            CORSMiddleware.default(),
            RouterMiddleware.new(routerConfig),
        ];
        errorSerializer = Liminal.defaultJsonErrorSerializer;
        candidRepresentationNegotiator = Liminal.defaultCandidRepresentationNegotiator;
        logger = Liminal.buildDebugLogger(#info);
    });

    // Expose standard HTTP interface
    public query func http_request(request : Liminal.RawQueryHttpRequest) : async Liminal.RawQueryHttpResponse {
        app.http_request(request)
    };

    public func http_request_update(request : Liminal.RawUpdateHttpRequest) : async Liminal.RawUpdateHttpResponse {
        await* app.http_request_update(request)
    };
}

Known Quirks

Before diving in, here are some current limitations and quirks to be aware of:

  • HTTP Assets Inflexibility: Asset canisters are somewhat rigid - they can only be accessed from one URL and require using the icx-asset syncing process for deployment
  • Limited dfx Asset Support: Unlike Rust asset canisters, Motoko asset canisters don’t have built-in dfx support, requiring manual setup
  • Verbose Asset Integration: Motoko requires manually implementing all asset canister HTTP endpoints, making the integration more verbose than ideal
  • Serialization Challenges: Motoko lacks automatic JSON/CBOR/XML serialization, so you either implement it manually or use the ‘serde’ library (which has some rough edges)
  • Query/Update Complexity: The automatic query→update upgrade process can be complex to understand - when an upgrade happens, the entire request starts over from the beginning of the middleware pipeline
  • No Auto API Mapping: There’s currently no automatic mapping between Candid APIs and HTTP APIs - you need to define both separately

These are largely ecosystem-level challenges rather than framework-specific issues, and the framework aims to smooth over what it can.

Documentation

The framework includes documentation with examples for:

  • Middleware Development: Creating custom middleware components
  • Routing Patterns: Path matching including wildcards and parameters
  • Authentication Flows: JWT validation and OAuth integration
  • Asset Management: Working with IC asset canisters
  • File Upload Handling: Processing multipart form data

Full documentation and examples are available in the GitHub repository. https://github.com/edjcase/liminal

Dependencies

Liminal builds on several Motoko libraries:

  • new-base (0.4.0) - Enhanced base library
  • jwt (1.0.1) - JWT token handling
  • compression (0.2.0) - Response compression
  • serde (3.2.2) - Serialization/deserialization
  • http-assets (0.0.7) - Asset canister integration
  • url-kit (0.0.9) - URL parsing utilities

All dependencies are automatically managed through MOPS.

License

Liminal is open source under the MIT License, allowing free use, modification, and distribution in both commercial and non-commercial projects.

Resources

Future Plans

The project is in active development with plans to:

  • Enhance Developer Experience: Gather community feedback to refine APIs and improve ergonomics
  • Expand Middleware Ecosystem: Add more built-in middleware for common use cases (validation, caching, etc.)
  • Query/Update Model Refinement: The automatic query→update upgrade system is new and untested at scale - looking for real-world feedback to optimize this approach
  • Performance Optimization: Benchmark and optimize the middleware pipeline performance
  • Tooling Integration: Develop dev tools and generators to streamline Liminal application development

The framework introduces an approach to handling IC’s query/update call model automatically through middleware, and I’m particularly interested in feedback from the community on this design pattern.


Have you tried building HTTP services on IC? I’d love to hear about your experiences and get feedback on whether Liminal addresses the pain points you’ve encountered!

19 Likes

Looks very cool at a glance! Thanks for publishing, I will keep it in mind for future projects.

2 Likes

Great contribution! I’ll give it a try for OAuth on-chain validation with JWTs :rocket:

1 Like

Looks, great. Very useful. Thanks for building this.

1 Like

Do you have open roles or need contributors ?
I would love to be a part of this

1 Like

@mahmudsudo No open roles, just myself right now
Always open to contributions on this or any of my other Motoko work
Send me a DM if you want to contribute or put up a PR on github with any changes

I am unable to dm and I will love to contribute

1 Like