Liminal
A collection of http Motoko libraries. Routing, parsing, middleware, etc...
Install / Use
/learn @edjCase/LiminalREADME
Liminal
A middleware-based HTTP framework for Motoko on the Internet Computer.
Overview
Liminal is a flexible HTTP framework designed to make building web applications with Motoko simpler and more maintainable. It provides a middleware pipeline architecture, built-in routing capabilities, and a variety of helper modules for common web development tasks.
Key features:
- 🔄 Middleware: Compose your application using reusable middleware components
- 🛣️ Routing: Powerful route matching with parameter extraction and group support
- 🔒 CORS Support: Configurable Cross-Origin Resource Sharing
- 🔐 CSP Support: Content Security Policy configuration
- 📦 Asset Canister Integration: Simplified interface with Internet Computer's certified assets
- 🔑 JWT Authentication: Built-in JWT parsing and validation
- 🚀 Compression: Automatic response compression for performance
- ⏱️ Rate Limiting: Protect your APIs from abuse
- 🛡️ Authentication: Configurable authentication requirements
- 🔀 Content Negotiation: Automatically convert data to JSON, CBOR, XML based on Accept header
- 📤 File Uploads: Parse and process multipart/form-data for handling file uploads (limited to 2MB)
- 📝 Logging: Built-in logging system with configurable levels and custom logger support
- 🔐 OAuth Authentication: Built-in OAuth 2.0 support with PKCE for Google, GitHub, and custom providers
Package
MOPS
mops add liminal
To setup MOPS package manager, follow the instructions from the MOPS Site
Liminal Middleware Pipeline
Liminal uses a middleware pipeline pattern where each middleware component processes requests as they flow down the pipeline, and then processes responses as they flow back up in reverse order. This creates a "sandwich" effect where the first middleware to see a request is the last to process the response.
Basic Flow Example
Request ──┐ ┌─> Response
│ |
▼ │
┌─────────────┐
- Decompresses │ Compression │ - Compresses
request │ Middleware │ response
└─────────────┘
│ ▲
▼ │
┌─────────────┐
- Parses JWT │ JWT │ - Ignores
- Sets identity │ Middleware │ response
└─────────────┘
│ ▲
▼ │
┌─────────────┐
- Matches url │ API Router │ - Returns API
to function │ Middleware │ response
└─────────────┘
How It Works
-
Request Flow (Down): The HTTP request starts at the first middleware and flows down through each middleware in the order they were defined in your middleware array.
-
Response Generation: Any middleware in the pipeline can choose to generate a response and stop the request flow. When this happens, the response immediately begins flowing back up through only the middleware that have already processed the request, bypassing any remaining middleware further down the pipeline.
-
Response Flow (Up): The response then flows back up through the middleware pipeline in reverse order, allowing each middleware to modify or enhance the response.
Query/Update Upgrade Flow
How Middleware Handles Query→Update Upgrades
In the Internet Computer, all HTTP requests start as Query calls (fast, read-only). If a middleware needs to modify state or make async calls, it can upgrade the request to an Update call (slower, can modify state). When this happens, the entire request restarts from the beginning with the same middleware pipeline.
Upgrade Flow Example
Query Flow Update Flow
Request ──┐ ┌──────► Request ──┐ ┌─► Response
│ │ │ │
▼ │ ▼ │
┌─────────────┐ │ ┌─────────────┐
│ Compression │ │ │ Compression │
│ Middleware │ │ │ Middleware │
└─────────────┘ │ └─────────────┘
│ │ │ ▲
▼ │ ▼ │
┌─────────────┐ │ ┌─────────────┐
│ JWT │ ──┘ │ JWT │
│ Middleware │ Upgrade │ Middleware │
└─────────────┘ └─────────────┘
│ ▲
▼ │
┌─────────────┐ ┌─────────────┐
│ API Router │ │ API Router │
│ Middleware │ │ Middleware │
└─────────────┘ └─────────────┘
How Upgrades Work
-
Query Processing: Request flows down through middleware as Query calls (fast path)
-
Upgrade Decision: Any middleware can decide it needs to upgrade (e.g., needs to modify state, make async calls)
-
Request Restart: When upgraded, the entire request restarts from the beginning as an Update call and go through each middleware again
Quick Start
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.get(
"/hello/{name}",
#query_(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 = [
// Order matters
// First middleware will be called FIRST with the HTTP request
// and LAST with handling the HTTP response
CORSMiddleware.default(),
RouterMiddleware.new(routerConfig),
];
errorSerializer = Liminal.defaultJsonErrorSerializer;
candidRepresentationNegotiator = Liminal.defaultCandidRepresentationNegotiator;
logger = Liminal.buildDebugLogger(#info);
urlNormalization = {
pathIsCaseSensitive = false;
preserveTrailingSlash = false;
queryKeysAreCaseSensitive = false;
removeEmptyPathSegments = true;
resolvePathDotSegments = true;
usernameIsCaseSensitive = false;
};
});
// 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)
};
}
More Complete Example
Here's a more comprehensive example demonstrating multiple middleware components:
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";
import JWTMiddleware "mo:liminal/Middleware/JWT";
import CompressionMiddleware "mo:liminal/Middleware/Compression";
import CSPMiddleware "mo:liminal/Middleware/CSP";
import AssetsMiddleware "mo:liminal/Middleware/Assets";
import SessionMiddleware "mo:liminal/Middleware/Session";
import HttpAssets "mo:http-assets";
actor {
// Define your routes
let routerConfig = {
prefix = ?"/api";
identityRequirement = null;
routes = [
Router.get(
"/public",
#query_(func(context : RouteContext.RouteContext) : Route.HttpResponse {
context.buildResponse(#ok, #text("Public endpoint"))
})
),
Router.groupWithAuthorization(
"/secure",
[
Router.get(
"/profile",
#query_(func(context : RouteContext.RouteContext) : Route.HttpResponse {
context.buildResponse(#ok, #text("Secure profile endpoint"))
})
)
],
#authenticated
)
]
};
];
// Initialize asset store
let canisterId = Principal.fromActor(self);
let assetStableData = HttpAssets.init_stable_store(canisterId, initializer)
|> HttpAssets.upgrade_stable_store(_);
let assetStore = HttpAssets.Assets(assetStableData, ?setPermissions);
// Create the HTTP App with middleware
let app = Liminal.App({
middleware = [
// Order matters - middleware are executed in this order for requests
// and in reverse order for responses
CompressionMiddleware.default(),
CORSMiddleware.default(),
SessionMiddleware.inMemoryDefault(),
JWTMiddleware.new({
locations = JWTMiddleware.defaultLocations;
validation = {
audience = #skip;
Related Skills
node-connect
354.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
112.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
354.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
354.2kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
