Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 25 additions & 25 deletions bench/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const port = Number(portArg ?? 3001);

function printUsage() {
console.log("Usage: bun bench/run.js <engine> <scenario> <port>");
console.log("Engines: http-native | bun | old | xitca | monoio | zig");
console.log("Engines: http-native | bun | xitca | monoio | zig");
console.log("Scenarios: static | dynamic | opt");
console.log("");
console.log("Example:");
Expand All @@ -30,7 +30,7 @@ function benchmarkPathForScenario(activeScenario) {
}

async function main() {
if (!["http-native", "bun", "old", "xitca", "monoio", "zig"].includes(engine)) {
if (!["http-native", "bun", "xitca", "monoio", "zig"].includes(engine)) {
printUsage();
process.exit(1);
}
Expand All @@ -43,33 +43,33 @@ async function main() {
const child =
engine === "xitca" || engine === "monoio"
? spawn(
"cargo",
[
"run",
"--release",
"--manifest-path",
engine === "xitca"
? "bench/xitca-server/Cargo.toml"
: "bench/monoio-server/Cargo.toml",
"--",
scenario,
String(port),
],
"cargo",
[
"run",
"--release",
"--manifest-path",
engine === "xitca"
? "bench/xitca-server/Cargo.toml"
: "bench/monoio-server/Cargo.toml",
"--",
scenario,
String(port),
],
{
cwd: process.cwd(),
stdio: ["ignore", "pipe", "inherit"],
},
)
: engine === "zig"
? spawn(
"zig",
["build", "run", "-Doptimize=ReleaseFast", "--", scenario, String(port)],
{
cwd: process.cwd(),
cwd: `${process.cwd()}/bench/zig-httpz`,
stdio: ["ignore", "pipe", "inherit"],
},
)
: engine === "zig"
? spawn(
"zig",
["build", "run", "-Doptimize=ReleaseFast", "--", scenario, String(port)],
{
cwd: `${process.cwd()}/bench/zig-httpz`,
stdio: ["ignore", "pipe", "inherit"],
},
)
: spawn("bun", ["bench/target.js", engine, scenario, String(port)], {
: spawn("bun", ["bench/target.js", engine, scenario, String(port)], {
cwd: process.cwd(),
stdio: ["ignore", "pipe", "inherit"],
});
Expand Down
27 changes: 27 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# http-native Examples

Each folder contains a self-contained example you can run with `bun`:

```bash
# Make sure to build the native module first
bun run build

# Then run any example
bun examples/basic/server.js
bun examples/cors/server.js
bun examples/middleware/server.js
bun examples/rest-api/server.js
bun examples/validation/server.js
bun examples/error-handling/server.js
```

## Examples

| Example | Description |
|---------|-------------|
| **[basic](./basic/)** | Routes, params, query strings, status codes, custom headers |
| **[cors](./cors/)** | CORS with wildcard, specific origins, dynamic origins, credentials |
| **[middleware](./middleware/)** | Logging, path-scoped auth, request IDs, `res.locals` data passing |
| **[rest-api](./rest-api/)** | Full CRUD Todo API — GET, POST, PUT, PATCH, DELETE with body parsing |
| **[validation](./validation/)** | Request body & query validation (Zod-compatible schema interface) |
| **[error-handling](./error-handling/)** | Custom error classes, global `onError()` handler, async error catching |
54 changes: 54 additions & 0 deletions examples/basic/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { createApp } from "http-native";

const app = createApp();

// Simple GET route
app.get("/", (req, res) => {
res.json({ message: "Hello, World!", timestamp: Date.now() });
});

// Route with params
app.get("/hello/:name", (req, res) => {
res.json({ greeting: `Hello, ${req.params.name}!` });
});

// Multiple params
app.get("/users/:userId/posts/:postId", (req, res) => {
res.json({
userId: req.params.userId,
postId: req.params.postId,
});
});

// Query string parsing
app.get("/search", (req, res) => {
const { q, page, limit } = req.query;
res.json({
query: q ?? "",
page: Number(page) || 1,
limit: Number(limit) || 10,
});
});

// Different status codes
app.get("/not-found", (req, res) => {
res.status(404).json({ error: "Resource not found" });
});

// Custom headers
app.get("/custom-headers", (req, res) => {
res.set("X-Custom-Header", "http-native")
.set("X-Request-Id", crypto.randomUUID())
.json({ headers: "set!" });
});

const server = await app.listen({ port: 3000 });
console.log(`🚀 Basic server running at ${server.url}`);
console.log(`
Try these routes:
curl ${server.url}/
curl ${server.url}/hello/world
curl ${server.url}/users/42/posts/7
curl ${server.url}/search?q=http-native&page=2&limit=20
curl -v ${server.url}/custom-headers
`);
72 changes: 72 additions & 0 deletions examples/cors/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { createApp } from "http-native";
import { cors } from "http-native/cors";

const app = createApp();

// ─── Example 1: Allow all origins ─────────────────────────────────────────────
// app.use(cors());

// ─── Example 2: Allow specific origin ─────────────────────────────────────────
// app.use(cors({ origin: "https://myapp.com" }));

// ─── Example 3: Allow multiple origins ────────────────────────────────────────
// app.use(cors({ origin: ["https://myapp.com", "https://admin.myapp.com"] }));

// ─── Example 4: Dynamic origin with credentials ──────────────────────────────
const ALLOWED_ORIGINS = new Set([
"http://localhost:3000",
"http://localhost:5173",
"https://myapp.com",
]);

app.use(
cors({
origin: (requestOrigin) => ALLOWED_ORIGINS.has(requestOrigin),
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "X-Request-Id"],
exposedHeaders: ["X-Request-Id", "X-RateLimit-Remaining"],
maxAge: 86400, // Cache preflight for 24 hours
}),
);

// ─── Routes ───────────────────────────────────────────────────────────────────

app.get("/api/data", (req, res) => {
res.set("X-Request-Id", crypto.randomUUID()).json({
data: [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
],
});
});

app.post("/api/data", (req, res) => {
const body = req.json();
res.status(201).json({ created: true, item: body });
});

app.options("/api/data", (req, res) => {
// CORS middleware handles this automatically via preflight
res.status(204).send();
});

const server = await app.listen({ port: 3000 });
console.log(`🌐 CORS example running at ${server.url}`);
console.log(`
Test with:
# Simple GET (no CORS headers without Origin)
curl -v ${server.url}/api/data

# CORS preflight
curl -v -X OPTIONS \\
-H "Origin: http://localhost:5173" \\
-H "Access-Control-Request-Method: POST" \\
-H "Access-Control-Request-Headers: Content-Type" \\
${server.url}/api/data

# CORS GET
curl -v -H "Origin: http://localhost:5173" ${server.url}/api/data

# Disallowed origin (no CORS headers)
curl -v -H "Origin: https://evil.com" ${server.url}/api/data
`);
130 changes: 130 additions & 0 deletions examples/error-handling/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { createApp } from "http-native";

const app = createApp();

// ─── Custom Error Classes ─────────────────────────────────────────────────────

class AppError extends Error {
constructor(message, statusCode = 500, code = "INTERNAL_ERROR") {
super(message);
this.statusCode = statusCode;
this.code = code;
}
}

class NotFoundError extends AppError {
constructor(resource = "Resource") {
super(`${resource} not found`, 404, "NOT_FOUND");
}
}

class ValidationError extends AppError {
constructor(message) {
super(message, 400, "VALIDATION_ERROR");
}
}

class UnauthorizedError extends AppError {
constructor(message = "Authentication required") {
super(message, 401, "UNAUTHORIZED");
}
}

// ─── Global Error Handler ─────────────────────────────────────────────────────

app.onError((err, req, res) => {
// Known application errors
if (err instanceof AppError) {
console.error(`[${err.code}] ${req.method} ${req.path}: ${err.message}`);
res.status(err.statusCode).json({
error: {
code: err.code,
message: err.message,
},
});
return;
}

// Unexpected errors — log full stack, return generic message
console.error(`[UNHANDLED] ${req.method} ${req.path}:`, err);
res.status(500).json({
error: {
code: "INTERNAL_ERROR",
message:
process.env.NODE_ENV === "production"
? "An unexpected error occurred"
: err.message,
},
});
});

// ─── Routes ───────────────────────────────────────────────────────────────────

app.get("/", (req, res) => {
res.json({ status: "ok" });
});

// Throws a custom NotFoundError
app.get("/users/:id", (req, res) => {
const id = Number(req.params.id);
if (id !== 1) {
throw new NotFoundError("User");
}
res.json({ id: 1, name: "Alice" });
});

// Throws a ValidationError
app.post("/users", (req, res) => {
const body = req.json();
if (!body?.name) {
throw new ValidationError("Name is required");
}
if (body.name.length < 2) {
throw new ValidationError("Name must be at least 2 characters");
}
res.status(201).json({ id: 2, name: body.name });
});

// Throws an UnauthorizedError
app.get("/admin", (req, res) => {
const token = req.header("authorization");
if (!token) {
throw new UnauthorizedError();
}
res.json({ admin: true });
});

// Throws an unhandled error (caught by generic handler)
app.get("/crash", (req, res) => {
throw new TypeError("Cannot read property 'foo' of undefined");
});

// Async error (also caught!)
app.get("/async-crash", async (req, res) => {
await new Promise((resolve) => setTimeout(resolve, 10));
throw new Error("Async failure");
});

const server = await app.listen({ port: 3000 });
console.log(`🛡️ Error handling example running at ${server.url}`);
console.log(`
Try these:
# OK
curl ${server.url}/users/1

# 404 NotFoundError
curl ${server.url}/users/999

# 400 ValidationError
curl -X POST -H "Content-Type: application/json" \\
-d '{}' ${server.url}/users

# 401 UnauthorizedError
curl ${server.url}/admin

# 500 Unhandled error
curl ${server.url}/crash

# 500 Async error
curl ${server.url}/async-crash
`);
Loading