Reno

Reno is a thin routing library designed to sit on top of Deno's standard HTTP module.

import { serve } from "https://deno.land/std@v0.23.0/http/server.ts";

import {
  createRouter,
  AugmentedRequest,
  createRouteMap,
  textResponse,
  jsonResponse,
  streamResponse,
} from "https://raw.githubusercontent.com/jamesseanwright/reno/v0.6.1/reno/mod.ts";

export const routes = createRouteMap([
  ["/home", () => textResponse("Hello world!")],

  // Supports RegExp routes for further granularity
  [/^\/api\/swanson\/?([0-9]?)$/, async (req: AugmentedRequest) => {
    const [quotesCount = "1"] = req.routeParams;

    const res = await fetch(
      `https://ron-swanson-quotes.herokuapp.com/v2/quotes/${quotesCount}`,
    );

    return jsonResponse(await res.json());
  }],

  // Supports Reader for streaming responses in chunks
  ["/streamed-response", () => streamResponse(
    new ReactReader(<App />),
  )],
]);

const router = createRouter(routes);

(async () => {
  console.log("Listening for requests...");

  for await (const req of serve(":8001")) {
    req.respond(await router(req));
  }
})();

TODO: replace/complement this with proper documentation

Responses are Just Data Structures

This, along with request handlers being pure functions, makes unit testing Reno services a breeze:

import { jsonResponse, assertResponsesMatch } from "https://raw.githubusercontent.com/jamesseanwright/reno/v0.6.1/reno/mod.ts";
import { createRonSwansonQuoteHandler } from './routes.ts';

const createFetchStub = (response: string[]) =>
  sinon.stub().resolves({
    json: sinon.stub().resolves(response),
  });

test({
  name: "ronSwansonQuoteHandler should fetch a quote from an API and return it",
  async fn() {
    const quotes = ["Some Ron Swanson Quote"];
    const fetchStub = createFetchStub(quotes);
    const ronSwansonQuoteHandler = createRonSwansonQuoteHandler(fetchStub);

    const req = {
      routeParams: []
    };

    const response = await ronSwansonQuoteHandler(req);

    assertResponsesMatch(response, jsonResponse(quotes, {
      "X-Foo": "bar"
    }));
  }
});

pipe() - An Alternative to Middleware

Deno emulates the middleware pattern, found in Express, favouring function piping to create reusable, higher-order route handlers:

import { createRouteMap, jsonResponse, pipe } from "https://raw.githubusercontent.com/jamesseanwright/reno/v0.6.1/reno/mod.ts";

const withCaching = pipe(
  (req, res) => {
    /* Mutate the response returned by
     * the inner route handler... */
    res.headers.append("Cache-Control", "max-age=86400");
  },

  /* ...or go FP and return a new
   * response reference entirely. */
  (req, res) => ({
    ...res,
    cookies: new Map<string, string>([["requested_proto", req.proto]])
  })
);

const home = withCaching(() =>
  jsonResponse({
    foo: "bar",
    isLol: true
  })
);

export const routes = createRouteMap([["/", home]]);

Local Development

Once you've cloned the repository, you'll need to ensure you're running the version of Deno against which this project is developed; this is stored in .deno-version. To install the correct version, run:

$ curl -fsSL https://deno.land/x/install/install.sh | sh -s $(cat .deno-version)

You should also run ./tools/install-types.sh to install the TypeScript definitions for Deno and any other third-party dependencies.

Then you can run:

  • deno example/index.ts - starts the example server
  • deno test - runs the unit tests

Functionality Checklist

  • Path routing
  • Async-compatible route handlers
  • Error handling
  • Route params
  • Query params
  • Response helpers
  • JSON
  • Custom headers
  • Request bodies
  • Cookies
  • Streaming responses with Reader
  • Streaming request bodies