Spikard HTTP framework for WebAssembly and edge runtimes with full TypeScript support, targeting browsers, Deno, Cloudflare Workers, and Node.js.
npm install @spikard/wasm> Note: As of v0.2.1, this package has moved to @spikard/wasm. Update your imports from 'spikard-wasm' to '@spikard/wasm'. See MIGRATION-0.2.1.md for details.








Spikard HTTP framework compiled to WebAssembly with full TypeScript support for edge runtimes, browsers, and server-side JavaScript environments. Build type-safe web services that run anywhere.
- WASM-first: Compiled from Rust to WebAssembly for maximum performance and portability
- Type-safe routing: Full TypeScript support with auto-completed route definitions
- Edge runtime support: Works in browsers, Cloudflare Workers, Deno, and Node.js
- Zero Node.js dependencies: Pure fetch API—no Node globals required
- Async/await native: Seamless async/await for handlers and middleware
- Lightweight: Optimized WASM binaries with aggressive tree-shaking
- Schema validation: Built-in request/response validation with Zod
- WebSocket & SSE: Full support for real-time features on compatible runtimes
- Testing utilities: In-memory test client for easy unit testing
- Code generation: Generate TypeScript apps and tests from OpenAPI/AsyncAPI
``bash`
npm install @spikard/wasmor with yarn
yarn add @spikard/wasmor with pnpm
pnpm add @spikard/wasm
`bash`
cd packages/wasm
pnpm install
pnpm build # outputs to dist/
`typescript
import { Spikard, createFetchHandler, get, post } from "@spikard/wasm";
const app = new Spikard();
// Define routes with type safety
get("/hello", async (req) => ({
message: "Hello from the edge!",
timestamp: new Date().toISOString(),
}));
post("/echo", async (req) => {
const body = await req.json();
return { echo: body };
});
// Export as a Cloudflare Worker
export default {
fetch: createFetchHandler(app),
};
`
`typescript
import { Spikard, get, post } from "npm:@spikard/wasm@0.2.1";
const app = new Spikard();
get("/hello", async (req) => ({
message: "Hello from Deno",
}));
post("/api/users", async (req) => {
const data = await req.json();
return { created: true, id: Math.random() };
});
Deno.serve({ port: 8000 }, (request) => app.handleRequest(request));
`
`typescript
import { Spikard, createFetchHandler, get } from "@spikard/wasm";
const app = new Spikard();
get("/api/status", async (req) => ({
status: "ok",
runtime: "node",
}));
const server = Bun.serve({
port: 3000,
fetch: createFetchHandler(app),
});
console.log(Server running on http://localhost:${server.port});`
`typescript
import { Spikard, get } from "@spikard/wasm";
const app = new Spikard();
get("/worker", async (req) => ({
message: "Running in a browser Web Worker",
}));
// Simulate incoming requests in a worker context
self.addEventListener("message", async (event) => {
const response = await app.handleRequest(event.data.request);
self.postMessage({ response });
});
`
`typescript
import { Spikard, get, post, put, patch, delete_, head, options } from "@spikard/wasm";
const app = new Spikard();
// Define routes with automatic method binding
get("/users", async (req) => {
// GET /users
});
post("/users", async (req) => {
// POST /users with body parsing
const body = await req.json();
});
put("/users/:id", async (req, { id }) => {
// PUT /users/:id with path params
});
patch("/users/:id", async (req, { id }) => {
// PATCH /users/:id
});
delete_("/users/:id", async (req, { id }) => {
// DELETE /users/:id (note: delete_ to avoid keyword)
});
`
`typescript
// Access request properties
get("/example", async (req) => {
const method = req.method; // "GET"
const url = req.url; // Full URL
const headers = req.headers; // Headers object
// Parse JSON body
const json = await req.json();
// Parse form data
const form = await req.formData();
// Get raw text
const text = await req.text();
// Get ArrayBuffer
const buffer = await req.arrayBuffer();
return { received: true };
});
`
`typescript
import { Spikard, get, json, status, withHeaders } from "@spikard/wasm";
get("/users", async (req) => {
return json(
{
users: [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
],
},
{
status: 200,
headers: {
"X-Total-Count": "2",
"Cache-Control": "max-age=3600",
},
}
);
});
get("/created", async (req) => {
return status(201, { id: 123, created: true });
});
`
`typescript
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
post("/users", async (req) => {
const body = await req.json();
// Validate with Zod
const result = userSchema.safeParse(body);
if (!result.success) {
return {
error: "Invalid user data",
issues: result.error.issues,
};
}
// result.data is now type-safe
const user = result.data;
return json({ id: 1, ...user }, { status: 201 });
});
`
`typescript
import { describe, it, expect } from "vitest";
import { Spikard, TestClient, get } from "@spikard/wasm";
describe("API routes", () => {
const app = new Spikard();
get("/hello", async () => ({
message: "Hello",
}));
const client = new TestClient(app);
it("returns greeting", async () => {
const res = await client.get("/hello");
expect(res.status).toBe(200);
expect(res.json()).toEqual({
message: "Hello",
});
});
it("handles POST with body", async () => {
post("/echo", async (req) => {
const body = await req.json();
return { echo: body };
});
const res = await client.post("/echo", { message: "test" });
expect(res.status).toBe(200);
expect(res.json()).toEqual({
echo: { message: "test" },
});
});
});
`
Optimized for minimal bundle size:
- Uncompressed: ~200KB (varies by feature set)
- Gzip: ~60KB
- Brotli: ~45KB
Bundle size analysis:
`bash`Use source-map-explorer or similar
npx source-map-explorer 'dist/*/.js'
Compiled with aggressive optimizations in Cargo.toml:
`toml`
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-O3", "--enable-bulk-memory", "--enable-nontrapping-float-to-int", "--enable-simd"]
Build options:
`bashDevelopment (debug symbols, fast compile)
wasm-pack build --dev
Code Generation
Generate TypeScript applications and tests from OpenAPI/AsyncAPI specifications:
`bash
Generate from OpenAPI spec
spikard generate openapi \
--fixtures ../../testing_data \
--output ./generatedGenerate WebSocket handlers from AsyncAPI
spikard generate asyncapi \
--fixtures ../../testing_data/websockets \
--output ./generated
`Lifecycle Hooks
`typescript
import { Spikard, HookTypes } from "@spikard/wasm";const app = new Spikard();
// On every request (before validation)
app.onRequest(async (req) => {
console.log(
${req.method} ${req.url});
});// Before handler execution
app.preHandler(async (req) => {
// Add request ID, timing, etc.
});
// After response
app.onResponse(async (req, res) => {
console.log(
${req.method} ${req.url} -> ${res.status});
});// On error
app.onError(async (error, req) => {
console.error(
Error: ${error.message});
return { error: "Internal Server Error" };
});
`Real-Time Features
$3
`typescript
import { Spikard, ws } from "@spikard/wasm";const app = new Spikard();
ws("/chat", {
onOpen: (socket) => {
console.log("Client connected");
},
onMessage: (socket, data) => {
socket.broadcast(data);
},
onClose: (socket) => {
console.log("Client disconnected");
},
});
`$3
`typescript
import { Spikard, sse } from "@spikard/wasm";const app = new Spikard();
sse("/events", async (req, res) => {
res.write("data: " + JSON.stringify({ event: "connected" }) + "\n\n");
const interval = setInterval(() => {
res.write(
"data: " + JSON.stringify({ event: "ping", time: Date.now() }) + "\n\n"
);
}, 5000);
return () => clearInterval(interval);
});
`Error Handling
`typescript
import { Spikard, HttpError } from "@spikard/wasm";const app = new Spikard();
get("/users/:id", async (req, { id }) => {
if (!id) {
throw new HttpError(400, "User ID is required");
}
const user = await fetchUser(id);
if (!user) {
throw new HttpError(404,
User ${id} not found);
} return user;
});
// Automatic error response
// 404 -> { error: "User 123 not found", status: 404 }
`CI Benchmarks (2025-12-20)
Run:
snapshots/benchmarks/20397054933 (commit 25e4fdf, oha, 50 concurrency, 10s, Linux x86_64).| Metric | Value |
| --- | --- |
| Avg RPS (all workloads) | 10,658 |
| Avg latency (ms) | 5.70 |
Category breakdown:
| Category | Avg RPS | Avg latency (ms) |
| --- | --- | --- |
| path-params | 15,841 | 3.19 |
| multipart | 10,838 | 5.24 |
| query-params | 8,082 | 6.88 |
| json-bodies | 6,890 | 7.37 |
| forms | 6,241 | 8.80 |
Performance Tips
1. Lazy load routes: Only define routes you need to minimize WASM size
2. Compression: Enable Brotli/Gzip compression for responses
3. Caching: Use Cache-Control headers for static content
4. Streaming: For large responses, use streaming responses
5. Worker threads: Offload heavy computation to Web Workers
Environment Variables
Access environment variables based on runtime:
`typescript
// Cloudflare Workers
get("/env", async (req, { env }) => {
const apiKey = env.API_KEY;
return { apiKey };
});// Deno
get("/env", async (req) => {
const apiKey = Deno.env.get("API_KEY");
return { apiKey };
});
// Node.js / Bun
get("/env", async (req) => {
const apiKey = process.env.API_KEY;
return { apiKey };
});
`Debugging
Enable debug logging:
`typescript
const app = new Spikard({ debug: true });// Or via environment
// SPIKARD_DEBUG=1 npm run dev
`Examples
Full working examples for different runtimes:
- Rollup Bundler - Build with Rollup for browsers and Node.js
- Deno Runtime - Native Deno with zero build step
- Cloudflare Workers - Deploy to the edge with Wrangler
Each example includes:
- Complete TypeScript source code with strict typing
- Configuration files (tsconfig.json, package.json)
- Comprehensive README with usage instructions
- Consistent API routes demonstrating core features
examples/Testing
Run tests with Vitest:
`bash
pnpm test # Run all tests
pnpm test:watch # Watch mode
pnpm test:coverage # With coverage
`Test coverage minimum: 80%
Run integration tests:
`bash
task test:wasm
task test:wasm:integration
`Documentation
- API Docs: docs/api.md
- Architecture: docs/ARCHITECTURE.md
- Architecture Decision Records: ../../docs/adr/
- ADR 0001: Architecture & Layering
- ADR 0002: Tower-HTTP & Middleware
- ADR 0006: Async & Streaming
TypeScript Support
Full TypeScript support with strict type checking:
`bash
pnpm typecheck # Run tsc
pnpm lint # Run Biome
`Generated
.d.ts` files via wasm-bindgen for complete IDE support.Contributions welcome! See CONTRIBUTING.md
Code standards:
- TypeScript 5.x with strict mode enabled
- Biome for linting and formatting
- Vitest for testing
- 80%+ test coverage required
- @spikard/node: npm.im/@spikard/node - Node.js native bindings
- spikard: pypi.org/project/spikard - Python bindings
- spikard: rubygems.org/gems/spikard - Ruby bindings
- spikard/spikard: packagist.org/packages/spikard/spikard - PHP bindings
- spikard: crates.io/crates/spikard - Rust native
MIT - see LICENSE file