ranuts/node — 小さな HTTP フレームワーク

Node.js 向けの、依存なしの小さな HTTP ツール一式です。HTTP サーバー、ルーター、WebSocket サーバー、ボディと静的ファイルのミドルウェア、それに CLI とファイルシステムのヘルパーがいくつか入っています。

⚠️ Node 専用です。 このエントリーポイントは node:httpnode:fsnode:child_process などを読み込みます。ranuts/node から import してください。ブラウザー側のコードから読み込んではいけません。

読み込み

import { Server, Router, staticMiddleware, body } from 'ranuts/node';

ボディを解析するミドルウェアは body という名前で export されています(内部の名前は bodyMiddleware です)。bodyMiddleware という export はありません。

はじめの一歩

import { Server, Router, staticMiddleware, body } from 'ranuts/node';

const app = new Server();
const router = new Router();

// ルートはパスの完全一致で照合されます。ハンドラーはリクエストの Context を受け取ります。
router.get('/hello', (ctx) => {
  ctx.res.setHeader('Content-Type', 'application/json');
  ctx.res.end(JSON.stringify({ message: 'hello world' }));
});

// JSON ボディの POST。body() がそれを解析して ctx.request.body に載せます
router.post('/echo', (ctx) => {
  ctx.res.end(JSON.stringify({ youSent: ctx.request.body }));
});

// body() はリクエストボディを解析すると同時に、ルーターが読む ctx.request
// (method / path / url / query)も埋めます。だから router.routes() より前に登録します。
app.use(body());
app.use(router.routes());
app.use(router.allowedMethods());

// 静的ファイルの配信(`/` は ./public/index.html に振り分けられます)
app.use(staticMiddleware({ pathname: './public' }));

const server = app.listen(3000, () => {
  console.log('Server running at http://localhost:3000');
});
// `server` は土台になっている node:http の Server インスタンスです。

ミドルウェアの順番が効いてきます。Routerctx.request.pathctx.request.method を読みますが、それを埋めるのは body() だからです。まず body() を登録してください。付属の body() が今のところ解析できるリクエストボディは application/jsonmultipart/form-data です。

API

Server

デフォルト export。node:http の上に載せた、最小限の Koa 風サーバーです。

メンバー 説明
new Server() サーバーを作ります。引数は取りません。 () => Server
use(middleware) ミドルウェアを連なりの末尾に足します。返り値は void で、チェーンはできません。 (fn: MiddlewareFunction) => void
listen(...args) 待ち受けを始めます。引数はそのまま http.Server.listen へ渡され、土台の http.Server が返ります。 (...args) => http.Server
middleware 登録済みのミドルウェアの配列。 MiddlewareFunction[]
ctx 共有されるリクエストの Contextreq/res はリクエストごとに差し替わります)。 Context

ミドルウェアのシグネチャ

type Next = () => Promise<void> | Promise<never>;
type MiddlewareFunction = (ctx: Context, next: Next) => void | Promise<void>;

next() を呼ぶと次のミドルウェアへ制御が渡ります。ミドルウェアは登録した順に呼ばれ、next() を二度呼ぶと例外になります。

Context の中身

フィールド 説明
req 届いたリクエスト。 http.IncomingMessage
res サーバーの応答。res.setHeader / res.writeHead / res.end で書き込みます。 http.ServerResponse
ipv4() このマシンの、内部向けでない最初の IPv4 アドレスを返します(なければ undefined)。 () => string | undefined
request body() が足すもの。{ method, path, url, query, body } で、queryURLSearchParams です。 object (dynamic)
[key] Context は開いた入れ物です。ミドルウェアは好きなフィールドを足せます。 any

Router

デフォルト export。HTTP メソッドとパスの完全一致ごとにハンドラーを登録し、routes() でそれらをミドルウェアとして差し出します。

メソッド 説明
new Router() ルーターを作ります。 () => Router
get(url, handler) GET のルートを登録します。 (url: string, h: Handler) => void
post(url, handler) POST のルートを登録します。 (url: string, h: Handler) => void
put(url, handler) PUT のルートを登録します。 (url: string, h: Handler) => void
patch(url, handler) PATCH のルートを登録します。 (url: string, h: Handler) => void
del(url, handler) DELETE のルートを登録します。 (url: string, h: Handler) => void
head(url, handler) HEAD のルートを登録します。 (url: string, h: Handler) => void
options(url, handler) OPTIONS のルートを登録します。 (url: string, h: Handler) => void
routes() 一致したハンドラーへ振り分けるミドルウェアを返します。 () => MiddlewareFunction
allowedMethods() パスやメソッドが一致しなかったときに 404405501 を返すミドルウェアを返します。 () => MiddlewareFunction

ハンドラーのシグネチャ

type Handler = (ctx: Context, next: Next) => void;

リクエストのデータは ctx.requestmethodpathurlquerybody)から読み、応答は ctx.res を通して返します。パスは完全一致で照合されます。:param のようなセグメントには対応していないので、クエリパラメーターには ctx.request.query を使ってください。

ミドルウェア

シンボル 説明
body(options?) ボディを解析するミドルウェア。ctx.request を埋め、application/jsonmultipart/form-data を解析します。返り値はミドルウェアです。 (o?: Partial<ServerBody>) => MiddlewareFunction
staticMiddleware(opt?) opt.pathname(既定は process.cwd())から静的ファイルを配信し、/ には index.html を返します。 (o?: Partial<Option>) => MiddlewareFunction
connect(fn) Connect や Express 流の (req, res, next) ミドルウェアを、このフレームワークのミドルウェアに合わせます。 (fn) => MiddlewareFunction

body(options) のオプション

オプション 説明 既定値
uploadDir multipart/form-data でアップロードされたファイルの置き場。 string '.'
encoding 届くリクエストストリームのエンコーディング。 BufferEncoding 'utf-8'/'binary'
json JSON のボディを解析します(false なら生の文字列のまま)。 boolean true
urlencoded urlencoded のボディ用に予約されています。 boolean true

staticMiddleware(option) のオプション

オプション 説明 既定値
pathname ファイルを配信する起点のディレクトリ。 string process.cwd()
fileTypes 追加で登録する 拡張子 → MIME タイプ の対応。 Record<string, string> {}

WebSocket

シンボル 説明
new WSS(httpServer) WebSocket サーバーを node:http のサーバーに取り付けます(upgrade のハンドシェイクとフレーム処理を引き受けます)。 (server: http.Server) => WSS
import { Server, WSS } from 'ranuts/node';

const app = new Server();
const server = app.listen(3000);
const wss = new WSS(server);

wss.on('connect', (client) => {
  client.on('message', (data) => client.send('echo: ' + data));
});
// wss.broadcast(data) は接続中の全クライアントに送ります。wss.clients がその一覧です。

個々の clientsend(data, options?)ping()pong()close()socket を備え、messagecloseerror のイベントを発行します。

ユーティリティ

シンボル 説明 シグネチャ
connect(fn) Connect や Express の (req, res, next) ミドルウェアを、このフレームワークのミドルウェアに合わせます。 (fn) => MiddlewareFunction
get({ url }) JSON のエンドポイントを HTTPS で GET し、{ success, data, message } で解決します。 ({ url: string }) => Promise<Response>
getIPAdress() このマシンの、内部向けでない最初の IPv4 アドレス。なければ undefined () => string | undefined
paresUrl(req) req.url{ search, query, pathname, path, href } に分解します(綴りにご注意を)。 (req: IncomingMessage) => ParseUrl | undefined
prompt({ message }) ターミナルで yes/no を尋ね、yyes なら true で解決します。 ({ message, stream?, defaultResponse? }) => Promise<boolean>
runCommand(cmd, args) 子プロセスを起動し(stdio は引き継ぎます)、終了コード 0 で解決します。 (cmd: string, args: string[]) => Promise<void>
readStream({ path }) pathfs.ReadStream を作ります。 (o: { path: string, ... }) => ReadStream
writeStream({ path }) pathfs.WriteStream を作ります。 (o: { path: string, ... }) => WriteStream
startTask() 高分解能のタイマーを始め、中身の見えない symbol を返します。 () => symbol
taskEnd(symbol) 対応する startTask() からの経過時間(Node ではナノ秒の bigint)。 (s: symbol) => number | bigint
traverse(dir, cb, pre?) dir を再帰的にたどり、ファイルごとに cb(relPath, absPath, stats) を呼びます(非同期)。 (dir, cb, pre?) => Promise<any>
traverseSync(dir, cb, pre?) traverse の同期版。 (dir, cb, pre?) => void
isColorSupported 真偽値。今のターミナルが ANSI カラーに対応しているかどうか。 boolean
colors ANSI カラーのヘルパー。colors.red('text') のように使え、reset / bold / dim も揃っています。 Record<string, (s: string) => string>

関連ページ

同じ ranuts/node エントリーポイントには、別ページで説明しているファイルシステムのヘルパーも入っています。