Miniframe-Router: Router for Express.JS Applications

Ilya Zykin
3 min readDec 30, 2024

--

miniframe-router — a minimalist router for Express.JS applications, inspired by Ruby on Rails. Here’s how I made it.

Example of how routing might look in a simple application.

// Root routes
root("index#home");
// Basic CRUD routes
get("/users", "users#index");
get("/users/:id", "users#show");
post("/users", "users#create");
post("/users/:id", "users#update");
post("/users/:id/destroy", "users#destroy");

// Posts routes with scope
scope("blog", () => {
get("/posts", "posts#index");
get("/posts/:id", "posts#show");
post("/posts", "posts#create");
post("/posts/:id", "posts#update");
post("/posts/:id/destroy", "posts#destroy");
});

Context

I hadn’t written NodeJS applications for about 10 years. Curious about Telegram Mini Apps, I decided to build a few simple projects to explore this new field.

For this, I needed routing for the backend of my applications. Existing solutions didn’t quite impress me, so I decided to create my own router — simple, effective, and inspired by Ruby on Rails. It was also a great opportunity to refresh my knowledge of creating NPM packages, something I hadn’t done in 9 years.

Design

Expectations for the Router:

1. Routing is defined in a separate file

This simplifies the application structure and avoids cluttering the main application file.

import {
root,
get,
post,
routeScope as scope,
getRouter,
} from "miniframe-router";

// Root routes
root("index#home");

// Basic CRUD routes
get("/users", "users#index");
get("/users/:id", "users#show");
post("/users", "users#create");
post("/users/:id", "users#update");
post("/users/:id/destroy", "users#destroy");

// Posts routes with scope
scope("blog", () => {
get("/posts", "posts#index");
get("/posts/:id", "posts#show");
post("/posts", "posts#create");
post("/posts/:id", "posts#update");
post("/posts/:id/destroy", "posts#destroy");
});

export default getRouter;

2. The router integrates into the main ExpressJS file

A simple and effective entry point for the application:
src/index.js or src/main.js.

import express from "express";
import getRouter from "./routes"; // <<< DEFINE ROUTES

const app = express();
app.use(express.json());
app.use(getRouter()); // <<< USER ROUTES
app.listen(3000, () => {
console.log("Demo app is running on http://localhost:3000");
});

3. Following conventions for connecting controllers

Controllers are located in the src/controllers directory.

MyApp
└── src
├── controllers
│ ├── blog
│ │ └── postsController.ts
│ ├── indexController.ts
│ └── usersController.ts
├── index.ts
└── routes
└── index.ts

File Structure

  • Routes are defined in src/routes/index.js.
  • Controllers contain the logic for handling requests:

Example: src/controllers/usersController.ts (or .js).

import { Request, Response } from "express";

export const index = (req: Request, res: Response) => {
res.send("List of all users");
};

export const show = (req: Request, res: Response) => {
const { id } = req.params;
res.send(`Showing details for user ${id}`);
};

export const create = (req: Request, res: Response) => {
const userData = req.body;
res.send(`Creating new user with data: ${JSON.stringify(userData)}`);
};

export const update = (req: Request, res: Response) => {
const { id } = req.params;
const userData = req.body;
res.send(`Updating user ${id} with data: ${JSON.stringify(userData)}`);
};

export const destroy = (req: Request, res: Response) => {
const { id } = req.params;
res.send(`Deleting user ${id}`);
};

Implementation

Steps:

  1. Create a development container using Docker.
  2. Set up ESBuild for compiling TypeScript to JavaScript.
  3. Install Jest for testing.
  4. Write tests to validate the first version of the router.
  5. Implement the router until all tests pass.
  6. Recover my long-unused NPM credentials (after 9 years).
  7. Publish the NPM package as version 1.0.0.
  8. Fix critical installation bugs and remove versions 1.0.0 and 1.1.0.
  9. Apply the router to a production project with a dozen routes.

Q&A

Why even do this? There are so many existing solutions!

  • It’s interesting, useful, and contributes to diversity in solutions.

Why so simple? Shouldn’t it be more complex?

  • Following the KISS principle. Complexity can be added later as needed.

Why controllers? Aren’t they unnecessary?

  • Controllers are convenient for small projects. Higher abstractions like Action/Operation-like solutions are better suited for larger systems.

Is this solution even functional?

  • Absolutely! The router was used in a small production app and performed perfectly with dozens of routes.

What size projects can it handle?

  • miniframe-router is suitable for applications with dozens of routes. There are no strict size limitations.

Conclusion

Project code and documentation: GitHub — miniframe-router

NPM package: miniframe-router

Like, share, and subscribe! Constructive feedback is always welcome.

Author’s page: GitHub — the-teacher

--

--

Ilya Zykin
Ilya Zykin

Written by Ilya Zykin

IT coach. React and Rails enthusiast. Passionate programmer, caring husband and pancake baker on Sundays. School teacher of computer studies in the past.

No responses yet