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
Middleware Pipeline: Compose your application using reusable middleware components - create custom middleware or use built-in ones, with automatic query/update flow handling
Advanced Routing: Route matching with parameter extraction (
/users/{id}
), wildcards (*
,**
), and nested route groupsSecurity: Built-in CORS, CSP (Content Security Policy), CSRF protection, and rate limiting
Authentication: JWT parsing/validation and OAuth 2.0 with PKCE support (Google, GitHub, custom providers)
Asset Integration: Interface with Internet Computer’s certified asset canisters
Performance: Automatic response compression (gzip) and content negotiation
File Uploads: Parse multipart/form-data for handling file uploads (up to 2MB)
Content Negotiation: Automatically convert Candid data to JSON, CBOR, XML based on Accept headers
Session Management: Configurable session storage with cookie handling
Rate Limiting: Protect APIs from abuse with flexible rate limiting strategies
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
- GitHub Repository: https://github.com/edjcase/liminal
- MOPS Package: https://mops.one/liminal
- Examples: Complete working examples in the
/examples
directory - API Documentation: Inline documentation throughout the codebase
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!