@cursorsdottsx/express

Express.js, but for the client side.

Usage no npm install needed!

<script type="module">
  import cursorsdottsxExpress from 'https://cdn.skypack.dev/@cursorsdottsx/express';
</script>

README

@cursorsdottsx/express

Express.js, but for the client side.

Introduction

@cursorsdottsx/express brings the minimalistic Express.js API to the frontend, so that client side routing isn't an overhead at all.

It has an API similar to Express, but naturally, it does not have as many features because it is on the frontend.

Installation and usage

Installing is very easy with NPM or Yarn:

npm install @cursorsdottsx/express
yarn add @cursorsdottsx/express

Using it is equally as easy:

import express from "@cursorsdottsx/express";

const app = express();

Documentation

There's a small example available in the GitHub repository that you can use to get a glimpse of what this looks like in action, so make sure to check that out. If you don't get what's happening, come back and take a look at the documentation.

Definitions

express(options)Creates a new app.

  • options – App options.

    • hash – Use the hash for client side routing instead.
    • attribute – Attribute to look for to register links.
    • default – Default template if no route matches.
    • error – Template to render if an error occured.
    • sanitizer – Sanitizer to purify templates.

App#use(path, template, callback)Registers a new route.

  • path – Route's path to match.
  • template – The template to render.
  • callback – Used to provide props and/or an alternative template.

App#listen()Starts the app and adds event listeners.

App#update()Triggers an update in the app.

App#goto(path)Navigates to the provided path.

  • path – Path to go to.

Basic routing

It's literally the same as Express.js, but if you're too lazy to go over it again, here's a quick recap:

  • / – Root.
  • /route – A route.
  • /a/route – Another route.
  • /param/:foo – Route parameter.
  • /param/:foo/:bar – Two route parameters.

Route callbacks

The third parameter to App#use is called when the route gets matched. Its return value is used to decide what gets rendered. Since its optional, if it is not provided, the route's default template will be rendered.

Execution context

The callback will receive one parameter, the execution context. This object contains the following:

  • url – An instance of URL of the current url.
  • params – Route parameters.
  • query – URL query.

Not found

You can choose to display a 404 if you wish, by either returning false, or { notFound: true }. This will display the next route if it matches, and so on and so on. If no routes match, the default template will be rendered.

app.use(
    "/secret/:id",
    `
        <p>This is a secret!</p>
    `,
    async (ctx) => {
        const user = await queryDatabase();

        if (!user) return { notFound: true };

        return { ... };
    }
);

Props

Along with displaying a 404, you can also pass props to the template. All you have to do is return a props object with the desired keys and values. Keys must satisfy this regex, however: /^[$A-Za-z0-9_-]+$/. Values can be any primitive, or an object with more keys and values.

app.use(
    "/secret/:id",
    `
        <p>This is a secret!</p>
    `,
    async (ctx) => {
        const user = await queryDatabase();

        if (!user) return { notFound: true };

        return {
            props: {
                id: ctx.params.id,
                user: {
                    username: user.username,
                },
            },
        };
    }
);

Templating

Finally, we have templating. Note that this does not sanitize input and does a raw search and replace. You can pass a sanitizer to the app to sanitize input yourself with a library like DOMPurify. Templating is very easy; it's simply {prop}. Accessing nested values is also possible using dot notation.

app.use(
    "/secret/:id",
    `
        <h1>Hi {user.username}!</h1>

        <p>This is a secret! ID: {id}</p>
    `,
    async (ctx) => {
        const user = await queryDatabase();

        if (!user) return { notFound: true };

        return {
            props: {
                id: ctx.params.id,
                user: {
                    username: user.username,
                },
            },
        };
    }
);

Contributing

Inside the folder src/lib is the actual library that is published to NPM. src/app contains the code for the demonstration and example. When contributing, make sure to edit the lib folder and make any necessary changes to the app folder if the API was modified.

When you are done, open a pull request and we'll look it at right away.

Make sure to include/describe the new feature, bug fix, or addition to the library.

Starting the app

To build the TypeScript with webpack, execute the following command:

yarn build

Then serve the root directory and enjoy the result. I used serve . in my case, but you could also use python3 -m http.server 5000.

Made with ❤️ by cursorsdottsx