vanilla-ts-http-route-handler
How to correctly add route handlers in a vanilla TypeScript project that uses http.createServer with a manual switch/case router — without introducing Express Router or Next.js patterns.
- License 许可
- MIT
- 所属仓库
- HKUDS/AgentPilot
- 最近更新
- 2026-11-12
- Stars
- ★ 8.0k
- Forks
- 941
01 前言 Front Matter
- name
- vanilla-ts-http-route-handler
- description
- How to correctly add route handlers in a vanilla TypeScript project that uses http.createServer with a manual switch/case router — without introducing Express Router or Next.js patterns.
02 资源文件 1
- 其他.skill_id43 B
03 SKILL.md 正文
Adding Route Handlers to a Vanilla TypeScript HTTP Server
Use this skill whenever you need to add a new route to a TypeScript project
that uses Node's built-in http module and a switch/case pathname router
without Express or Next.js.
Step 0 — Confirm the routing style
Before writing any code, inspect server/index.ts (or whichever file starts
the HTTP server). Look for:
http.createServer((req, res) => {
const { pathname } = new URL(req.url!, `http://${req.headers.host}`);
switch (pathname) {
case '/api/foo': ...
}
});
If you see this pattern, follow the steps below.
Do NOT use express.Router, next/server, or any framework-specific
handler signature — even if other files in server/routes/ happen to
use those patterns.
Step 1 — Create the route file
Create server/routes/<feature>.ts.
Use only the native Node types: http.IncomingMessage and
http.ServerResponse.
// server/routes/health.ts
import http from 'http';
export async function handleHealth(
req: http.IncomingMessage,
res: http.ServerResponse
): Promise<void> {
// Parse body for POST/PUT if needed
const body = await readBody(req); // helper shown in Step 3
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok' }));
}
Rules for the route file:
- Export the handler as a named async function (
handle<Feature>). - Accept exactly
(req: http.IncomingMessage, res: http.ServerResponse). - Return
Promise<void>. - Do not call
next()or return aResponseobject. - Handle errors internally and write an appropriate HTTP status + JSON body.
Step 2 — Import the handler in server/index.ts
Add a named import at the top of the file alongside any existing imports:
import { handleHealth } from './routes/health';
Step 3 — Add a case entry to the switch block
Locate the existing switch (pathname) block and add a new case:
switch (pathname) {
case '/api/health':
await handleHealth(req, res);
break;
// … existing cases …
default:
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
}
- Match the exact pathname string used by the client.
- Always
break(orreturn) after calling the handler. - Keep the
defaultcase last.
Step 4 — Optional: shared body-reading helper
If multiple routes need to parse a JSON request body, add a small utility rather than duplicating the logic:
// server/utils/readBody.ts
import http from 'http';
export function readBody(req: http.IncomingMessage): Promise<unknown> {
return new Promise((resolve, reject) => {
let data = '';
req.on('data', (chunk) => (data += chunk));
req.on('end', () => {
try {
resolve(data ? JSON.parse(data) : {});
} catch {
reject(new Error('Invalid JSON'));
}
});
req.on('error', reject);
});
}
Import and use it inside any route handler that needs it.
Checklist
- [ ]
server/routes/<feature>.tsexists and exports a named async handler. - [ ] Handler signature is
(req: http.IncomingMessage, res: http.ServerResponse): Promise<void>. - [ ] Handler always calls
res.writeHead(...)andres.end(...)on every code path. - [ ] Handler is imported in
server/index.ts. - [ ] A matching
caseentry is present in theswitch (pathname)block. - [ ] No Express / Next.js / Koa types or patterns were introduced.
Anti-patterns to avoid
| ❌ Wrong | ✅ Correct |
|---|---|
import { Router } from 'express' |
import http from 'http' |
export default function handler(req: NextApiRequest, ...) |
export async function handleFoo(req: http.IncomingMessage, ...) |
router.get('/foo', ...) |
case '/api/foo': await handleFoo(req, res); break; |
| Returning a value from the handler | Calling res.end(...) and returning void |
04 同仓库更多 Skills
agentpilot
Set up AgentPilot and use its single entry to prepare missing inputs and run configured RSI experiments. Use when the user requests AgentPilot installation or work with AgentPilot.
remember
Review auto-memory entries and propose promotions to OPENSPACE.md, OPENSPACE.local.md, or retained auto-memory topics; detect duplicates, stale facts, conflicts, and ambiguous entries before making changes.
skill-discovery
Search reusable OpenSpace skills locally, and browse cloud packages step by step when a cloud skill may help. Reusing proven skills saves tokens, improves reliability, and extends your capabilities beyond built-in tools.
delegate-task
Delegate tasks to OpenSpace — a full-stack autonomous worker for coding, DevOps, web research, and desktop automation, backed by an extensive MCP tool and skill library. Skills auto-improve through use, reducing token consumption over time. A cloud community lets agents share and collectively evolve reusable skills.
write-special-chars-ts-check
Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) using a Python heredoc script, plus a reliable TypeScript type-checking step that filters output to only the newly-written files to avoid being misled by pre-existing errors.
write-special-chars-readback
Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) using a Python heredoc script, with mandatory post-write verification via read_file and cleanup of /tmp helper scripts.