Aller au contenu

Créer un service HTTP

Servez du HTTP depuis Elide avec un gestionnaire fetch ou node:http : routage, corps JSON, streaming, fichiers statiques, TLS, plusieurs contextes et frameworks.

Sur cette page

Elide sert du HTTP depuis un gestionnaire fetch ou depuis node:http ; cette page construit un même service des deux façons.

Deux façons de servir

Gestionnaire fetchnode:http
Entréeexport default { fetch } ou Elide.serve({ fetch })http.createServer(listener).listen(port)
Requête et réponseRequest, Response du WebIncomingMessage, ServerResponse
HTTPS et HTTP/2Options cert et keyNon disponible
--contextsOuiNon ; la deuxième copie échoue avec EADDRINUSE
// server.ts
export default {
  port: Number(process.env.PORT ?? 3000),
  fetch(request: Request): Response {
    return new Response("hello\n");
  },
};
// node-server.ts
import { createServer } from "node:http";

const port = Number(process.env.PORT ?? 3000);

createServer((req, res) => {
  res.writeHead(200, { "content-type": "text/plain" });
  res.end("hello\n");
}).listen(port, "127.0.0.1");
elide run server.ts
curl -i http://127.0.0.1:3000/
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
content-length: 6
server: elide
date: Sat, 10 Oct 2026 00:18:12 GMT

hello

elide run node-server.ts répond à la même requête avec Transfer-Encoding: chunked et Connection: keep-alive.

Adresse d’écoute de chaque style lorsque vous n’en indiquez aucune :

ServeurAdresse par défaut
export default { fetch } sous elide run127.0.0.1:8080
export default { fetch } sous elide serve / elide dev127.0.0.1:8080 / 127.0.0.1:3000 ; --host et --port la remplacent
Elide.serve({ port, fetch })0.0.0.0 sur port

Routes, JSON et streaming

URLPattern, Request.json(), Response.json() et les corps ReadableStream sont des globales. Ce serveur expose une API, une réponse en streaming et des fichiers statiques depuis public/ :

// app.ts
import { readFile } from "node:fs/promises";
import { extname, join, normalize } from "node:path";

const users = new URLPattern({ pathname: "/api/users/:id" });
const types: Record<string, string> = {
  ".html": "text/html; charset=utf-8",
  ".css": "text/css",
  ".js": "text/javascript",
};

async function api(request: Request): Promise<Response | null> {
  const url = new URL(request.url);
  const match = users.exec(url);
  if (match && request.method === "GET") {
    return Response.json({ id: match.pathname.groups.id });
  }
  if (url.pathname === "/api/users" && request.method === "POST") {
    const body = await request.json();
    return Response.json({ created: body.name }, { status: 201 });
  }
  if (url.pathname === "/api/ticks") {
    let n = 0;
    const stream = new ReadableStream({
      async pull(controller) {
        controller.enqueue(new TextEncoder().encode(`tick ${++n}\n`));
        await new Promise((r) => setTimeout(r, 200));
        if (n === 3) controller.close();
      },
    });
    return new Response(stream, { headers: { "content-type": "text/plain" } });
  }
  return null;
}

async function file(pathname: string): Promise<Response> {
  const path = join("public", normalize(pathname === "/" ? "/index.html" : pathname));
  try {
    const body = await readFile(path);
    return new Response(body, { headers: { "content-type": types[extname(path)] ?? "application/octet-stream" } });
  } catch {
    return new Response("not found\n", { status: 404 });
  }
}

export default {
  port: Number(process.env.PORT ?? 3000),
  async fetch(request: Request): Promise<Response> {
    return (await api(request)) ?? file(new URL(request.url).pathname);
  },
};
elide run app.ts
curl http://127.0.0.1:3000/api/users/42
curl -X POST -H 'content-type: application/json' -d '{"name":"ada"}' http://127.0.0.1:3000/api/users
curl -N http://127.0.0.1:3000/api/ticks
{"id":"42"}
{"created":"ada"}
tick 1
tick 2
tick 3

Le POST répond 201 Created. Les trois ticks arrivent à 200 ms d’intervalle. / renvoie public/index.html, et un fichier absent renvoie 404.

La même API sur node:http lit le corps avec node:stream/consumers et diffuse avec res.write :

// node-app.ts
import { createServer } from "node:http";
import { text } from "node:stream/consumers";

createServer(async (req, res) => {
  if (req.method === "POST" && req.url === "/api/users") {
    const body = JSON.parse(await text(req));
    res.writeHead(201, { "content-type": "application/json" });
    res.end(JSON.stringify({ created: body.name }));
    return;
  }
  if (req.url === "/api/ticks") {
    res.writeHead(200, { "content-type": "text/plain" });
    for (let n = 1; n <= 3; n++) {
      res.write(`tick ${n}\n`);
      await new Promise((r) => setTimeout(r, 200));
    }
    res.end();
    return;
  }
  res.writeHead(404).end();
}).listen(Number(process.env.PORT ?? 3000), "127.0.0.1");

Pour ne servir que des fichiers, utilisez elide serve ./public ; voir Serveur de développement.

Erreurs

Un gestionnaire fetch qui lève une exception ou dont la promesse est rejetée répond 500 Internal Server Error et continue de servir. L’erreur n’est pas affichée : interceptez-la et journalisez-la vous-même.

curl -i -d 'not json' http://127.0.0.1:3000/api/users
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
content-length: 21
server: elide
date: Sat, 10 Oct 2026 00:34:28 GMT

Internal Server Error

TLS

Passez des chaînes PEM en cert et key à Elide.serve, ou comme propriétés de l’objet exporté. La clé doit être au format PKCS#8 non chiffré (BEGIN PRIVATE KEY). Les clients qui proposent HTTP/2 l’obtiennent.

// tls.ts
import { readFileSync } from "node:fs";

Elide.serve({
  port: Number(process.env.PORT ?? 8443),
  cert: readFileSync("cert.pem", "utf8"),
  key: readFileSync("key.pem", "utf8"),
  fetch(request) {
    return new Response("hello over tls\n");
  },
});
elide run tls.ts
curl -i --cacert cert.pem https://localhost:8443/
HTTP/2 200
content-type: text/plain;charset=UTF-8
content-length: 15
server: elide
date: Sat, 10 Oct 2026 00:18:52 GMT

hello over tls

elide serve et elide dev n’ont pas d’options TLS, et elide.pkl n’a pas de paramètres TLS. https.createServer lève :

Error: HTTPS server is not supported in this runtime (no host TLS listener); use http.createServer behind a TLS terminator, or https client APIs.

Plusieurs contextes

--contexts=N exécute N copies d’une application à gestionnaire fetch sur un même port, sous Linux. Chaque copie a son propre état de module :

// counter.ts
let count = 0;

export default {
  fetch(): Response {
    count += 1;
    return new Response(`${count}\n`);
  },
};
elide serve --no-tui --contexts=4 counter.ts
for i in 1 2 3 4 5 6 7 8; do curl -s http://127.0.0.1:8080/; done
1
1
2
2
3
4
1
2

Gardez l’état partagé dans une base de données ou un autre service. --contexts=auto dimensionne selon les cœurs du CPU ; engine.contexts dans elide.pkl le fixe par projet (Contextes du moteur).

Frameworks

Installez-les avec elide install --ecosystems=npm.

FrameworkÉtat
HonoFonctionne : export default { fetch: app.fetch } ou export default app.
Express 5Le routage, res.json, express.raw() et la lecture manuelle de req fonctionnent. express.json() et express.text() arrêtent le processus avec TypeError: Buffer.isBuffer is not a function.
Fastify 5Échoue à l’import : Error: No such built-in module: node:http2.
// hono-app.ts
import { Hono } from "hono";

const app = new Hono();
app.get("/users/:id", (c) => c.json({ id: c.req.param("id") }));
app.post("/users", async (c) => c.json({ created: (await c.req.json()).name }, 201));

export default {
  port: Number(process.env.PORT ?? 3000),
  fetch: app.fetch,
};

Exemples

Étapes suivantes

Cette page vous a-t-elle aidé ?