DEV Community

Ilya N. Zykin
Ilya N. Zykin

Posted on

miniframe-router: Router for Express.JS Applications

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

Image description


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");
});
Enter fullscreen mode Exit fullscreen mode

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:

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;
Enter fullscreen mode Exit fullscreen mode

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()); // <<< APPLY ROUTES

app.listen(3000, () => {
  console.log("Demo app is running on http://localhost:3000");
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}`);
};
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)