Open-Sourced Workers Router (TypeScript) - cloudflare-router

Hello, fellow developers!

I have created an open-sourced library for those who want to route requests with ease using Cloudflare Workers. Originally the plan was to use this only for myself, but as I didn’t find other routers that I liked, I wanted to bring it to the public :slightly_smiling_face:

You can find the GitHub repository here:


Features

What’s so great about Cloudflare-router? Here are some of the reasons:

  • You can use Cloudflare-router very similarly to express apps
  • It’s written in TypeScript
  • You can have middlewares
  • (WIP) The repository is tied with continuous integration which runs the tests and ensures things are working properly
  • Easily testable to ensure expected functionality
  • It’s very easy to use and flexible to your needs

Installing

Pretty easy, run the following command:

npm install cloudflare-router --save

Usage

Also very easy, with very little boilerplate code required to make it work. Here’s a short example:

TypeScript version
import { Router } from "cloudflare-router";


const router = new Router();

router.get("/", (_request, response) => {
    return response.text(`Hello, world, from TypeScript!`);
});

addEventListener("fetch", event => {
    event.respondWith(
        router.serve(event.request)
            .then(returned => returned.response)
    );
});
JavaScript version
const { Router } = require("cloudflare-router");

const router = new Router();

router.get("/", (request, response) => {
    response.text(`Hello, world, from JavaScript!`);
});

addEventListener("fetch", event => {
    event.respondWith(
        router.serve(event.request)
            .then(returned => returned.response)
    );
});

Examples

Some examples that are working right off the bat (only TypeScript here):

Middlewares
const router = new Router();

// Note that middlewares are working on ALL methods
router.use("/", async (request, response) => {
    // The request instance has a separate "data" Map
    // so that it's easier to pass on arguments to the final handler
    
    // For example if you were to retrieve something from the database from the request
    const exampleRequest = await fetch("url");
    
    request.data.isAuthenticated = true;
});

router.get("/", (request, response) => {
    if (!data.isAuthenticated) {
        return response.statusCode(403)
            .json({
                error: "Not authenticated"
            });
    }
    
    response.text("You are authenticated :)");
});
Testing your application
const router = new Router({
    // Used for turning the data received into a Response instance
    // Instead, as we're testing the code, we're returning the plain
    // object.
    customResponseTransformer: a => a
});

const request = (url, method, options = {}) => router.serve({
    url,
    method,
    headers: [],
    ...options
});


// Middleware
router.use("/*", (req, res) => {
    req.data.set("hello", "world");
});

router.get("/", (req, res) => {
    res.text("hello!");
});

// Assuming this is a test with Jest
describe("testing application", () => {
    it("should return a string in the body", async () => {
        const response = await request("https://domain.com/", "GET");
        
        // Testing the middleware
        expect(response.routerRequest.data.get("hello"))
            .toBe("world");
        
        
        // Testing the response body
        expect(response)
            .toMatchObject({
                response: {
                    body: "hello!"
                }
            });
    });
});

Splitting into multiple files
// File: apiRouter.ts
const router = new Router();

router.get("/", (_request, response) => {
    response.json({
        status: "OK",
        message: "API is operational"
    });
});

router.get("/greet/:name", (request, response) => {
    response.json({
        success: true,
        message: `Hello, ${request.params!.name}`
    });
});

export default router;

// File: index.ts
import apiRouter from "./apiRouter";


const router = new Router();

// Routing all /api requests to the apiRouter
router.use("/api", router);

router.get("/", (_request, response) => {
    response.text("Welcome the our service! Go to /api to use the API!");
});

addEventListener("fetch", event => {
    event.respondWith(
        router.serve(event.request)
            .then(response => response.response)
    );
});

Issues, Bugs or Features?

Please file an issue, or even better, file a pull request and help contribute to the project!
I’m still yet to create tests that cover more of the library, but they’re coming :sunglasses:

That’s it

I hope you find the library useful, if something’s wrong, keep in mind it was created by an 18-year old in high school :sunglasses:

7 Likes

Neat and clean, plus easy to use. Well done mate! Thanks for sharing it with the community :smiling_face_with_three_hearts:

3 Likes

Starred the repo on github, will give it a shot. Thanks a lot for sharing!

1 Like

Starred! :slight_smile:

One question: it looks like perhaps you intended for the routes to return a promise:

serve(request: RouterRequest<AdditionalDataType> | IncomingRequest, additionalData?: AdditionalDataType, response?: RouterResponse<AdditionalDataType>): Promise<BuiltResponse<AdditionalDataType>>

Any documentation or examples for what a Promise<BuiltResponse<AdditionalDataType>> might actually look like?

Thank you!

Come join the router jam in Cloudflare Workers Discord server.
:pbjtrock: :pbjtrock: :pbjtrock:

Hello, thanks for giving Cloudflare-router a shot!

First of all, I’ve auto-generated some documentation so you can access it from a web browser, here:
https://visualizememe.github.io/cloudflare-router

BuiltResponse is basically an object that contains (among other things) the Cloudflare Response, and it’s accessibly through builtResponse.response . My apologies for not making this clear enough.