npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

elysia-nnn-router

v0.1.1

Published

A router plugin for Elysia framework

Downloads

258

Readme

elysia-nnn-router

npm version npm downloads license

English | Tiếng Việt

Phiên bản hiện tại: 0.1.0

Một plugin router cho Elysia framework, cho phép tự động quét và đăng ký các route từ cấu trúc thư mục với hỗ trợ middleware theo cấp độ thư mục.

Đặc điểm nổi bật

  • 🚀 Tự động quét và đăng ký routes từ cấu trúc thư mục
  • 🔄 Hỗ trợ tất cả HTTP methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
  • 🎯 Dynamic routes với cú pháp [param]
  • 🛡️ Middleware cascading theo cấu trúc thư mục
  • 🎪 Method-level middleware cho logic riêng từng route
  • ⚡ Hiệu suất cao với Bun
  • 📦 TypeScript support

Cài đặt

bun add elysia-nnn-router

Cách sử dụng cơ bản

  1. Tạo thư mục routes trong dự án của bạn
  2. Tổ chức các route theo cấu trúc thư mục:
routes/
  ├── _middleware.ts          # Global middleware
  ├── users/
  │   ├── _middleware.ts      # Users middleware
  │   ├── get.ts              # GET /users
  │   ├── post.ts             # POST /users
  │   └── [id]/
  │       ├── _middleware.ts  # User detail middleware
  │       ├── get.ts          # GET /users/:id
  │       ├── put.ts          # PUT /users/:id
  │       └── delete.ts       # DELETE /users/:id
  └── posts/
      ├── get.ts              # GET /posts
      └── post.ts             # POST /posts
  1. Sử dụng plugin trong ứng dụng:
import { Elysia } from "elysia";
import { nnnRouterPlugin } from "elysia-nnn-router";

const app = new Elysia();

app.use(nnnRouterPlugin());

app.listen(3000, () => {
  console.log("🚀 Server running at http://localhost:3000");
});

Tùy chọn cấu hình

app.use(
  nnnRouterPlugin({
    dir: "custom-routes", // Thư mục chứa routes (mặc định: "routes")
    prefix: "/api", // Prefix cho tất cả routes (mặc định: "")
  })
);

Ví dụ với prefix

// Với prefix: "/api"
// routes/users/get.ts -> GET /api/users
// routes/users/[id]/get.ts -> GET /api/users/:id

Quy ước đặt tên

Route Files

Tên file route phải khớp với HTTP method (không phân biệt hoa thường):

  • get.ts hoặc get.js → GET request
  • post.ts hoặc post.js → POST request
  • put.ts hoặc put.js → PUT request
  • delete.ts hoặc delete.js → DELETE request
  • patch.ts hoặc patch.js → PATCH request
  • options.ts hoặc options.js → OPTIONS request

Dynamic Routes

Sử dụng [tên_tham_số] cho dynamic routes:

routes/users/[id]/get.ts        → GET /users/:id
routes/posts/[slug]/get.ts      → GET /posts/:slug
routes/users/[id]/posts/get.ts  → GET /users/:id/posts

Middleware

File _middleware.ts trong thư mục sẽ áp dụng cho:

  • Tất cả routes trong thư mục đó
  • Tất cả routes trong các thư mục con

Cách viết Route Handler

Route handler là một function được export default:

// routes/users/get.ts
export default () => {
  return { users: [] };
};

// routes/users/[id]/get.ts
export default ({ params }) => {
  return { id: params.id };
};

// routes/users/post.ts
export default ({ body }) => {
  return { message: "User created", data: body };
};

Với async handler

// routes/users/get.ts
export default async ({ query }) => {
  const users = await db.users.findMany();
  return { users };
};

Cách viết Middleware

Middleware được export default dưới dạng array hoặc single function:

Single Middleware

// routes/_middleware.ts
export default (context) => {
  console.log(`${context.request.method} ${context.request.url}`);
};

Multiple Middlewares

// routes/_middleware.ts
export default [
  (context) => {
    console.log("Middleware 1");
  },
  (context) => {
    console.log("Middleware 2");
  },
];

Middleware với Authentication

// routes/admin/_middleware.ts
export default async ({ headers, error }) => {
  const token = headers.authorization?.replace("Bearer ", "");

  if (!token) {
    return error(401, { message: "Unauthorized" });
  }

  const user = await verifyToken(token);
  if (!user) {
    return error(401, { message: "Invalid token" });
  }

  // Token hợp lệ, tiếp tục xử lý
};

Method-Level Middleware (Middleware cấp Method)

TÍNH NĂNG MỚI 🎉 Bạn có thể định nghĩa middleware riêng cho từng route method bằng cách export biến middleware cùng với handler:

Single Method Middleware

// routes/users/post.ts
import { OptionalHandler } from "elysia";

// Middleware validation chỉ cho route này
export const middleware: OptionalHandler = ({ body, error }) => {
  if (!body.email || !body.name) {
    return error(400, { message: "Email và name là bắt buộc" });
  }
};

// Route handler
export default async ({ body }) => {
  const user = await db.users.create(body);
  return { message: "Tạo user thành công", user };
};

Multiple Method Middlewares

// routes/admin/users/delete.ts
import { OptionalHandler } from "elysia";

export const middleware: OptionalHandler[] = [
  // Kiểm tra user có phải super admin không
  ({ store, error }) => {
    if (store.user.role !== "super_admin") {
      return error(403, { message: "Chỉ super admin mới có thể xóa user" });
    }
  },
  // Log thao tác xóa
  ({ params, store }) => {
    console.log(`User ${store.user.id} đang cố xóa user ${params.id}`);
  },
];

export default async ({ params }) => {
  await db.users.delete(params.id);
  return { message: "Xóa user thành công" };
};

Middleware Cascading

Middleware được áp dụng theo thứ tự từ parent đến child, middleware cấp method chạy cuối cùng:

routes/
  ├── _middleware.ts          # [1] Chạy đầu tiên cho tất cả routes
  └── admin/
      ├── _middleware.ts      # [2] Chạy sau cho /admin/*
      └── users/
          ├── _middleware.ts  # [3] Chạy thứ ba cho /admin/users/*
          └── post.ts         # [4] Method middleware (nếu có export)
                              # [5] Route handler

Thứ tự thực thi: [1] → [2] → [3] → [4] Method Middleware → [5] Route Handler

Điều này cho phép bạn:

  • Chia sẻ logic chung qua directory middlewares
  • Thêm validation/logic riêng cho từng route method
  • Giữ route files tự đủ với các requirements riêng của chúng

Ví dụ hoàn chỉnh

// routes/_middleware.ts
export default [
  (context) => {
    console.log(
      `[${new Date().toISOString()}] ${context.request.method} ${context.path}`
    );
  },
];

// routes/api/_middleware.ts
export default async ({ headers, error }) => {
  const apiKey = headers["x-api-key"];

  if (!apiKey || apiKey !== process.env.API_KEY) {
    return error(403, { message: "Invalid API key" });
  }
};

// routes/api/users/_middleware.ts
export default async ({ headers, error, store }) => {
  const token = headers.authorization?.replace("Bearer ", "");

  if (!token) {
    return error(401, { message: "Unauthorized" });
  }

  const user = await verifyToken(token);
  if (!user) {
    return error(401, { message: "Invalid token" });
  }

  // Lưu user vào store để sử dụng trong route handler
  store.user = user;
};

// routes/api/users/get.ts
export default async ({ store }) => {
  const currentUser = store.user;
  const users = await db.users.findMany();

  return {
    currentUser: currentUser.email,
    users,
  };
};

// routes/api/users/[id]/get.ts
export default async ({ params, store, error }) => {
  const user = await db.users.findById(params.id);

  if (!user) {
    return error(404, { message: "User not found" });
  }

  return { user };
};

// routes/api/users/post.ts
import { OptionalHandler } from "elysia";

// Method-level middleware cho validation
export const middleware: OptionalHandler[] = [
  ({ body, error }) => {
    // Validate các field bắt buộc
    if (!body.email || !body.name) {
      return error(400, {
        message: "Email và name là bắt buộc",
      });
    }
  },
  ({ body, error }) => {
    // Validate định dạng email
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(body.email)) {
      return error(400, {
        message: "Định dạng email không hợp lệ",
      });
    }
  },
];

export default async ({ body, store }) => {
  const newUser = await db.users.create({
    ...body,
    createdBy: store.user.id,
  });

  return {
    message: "Tạo user thành công",
    user: newUser,
  };
};

Hiệu Năng

elysia-nnn-router được thiết kế cho hiệu năng cao với overhead tối thiểu:

Hiệu Năng Runtime ⚡

  • Throughput: ~1,000,000 requests/giây
  • Latency: 0.001ms mỗi request
  • Overhead: 0% so với Elysia routing gốc
  • Status: Hiệu năng production-ready

Hiệu Năng Startup 🚀

| Routes | Thời Gian Startup | Memory Usage | |--------|-------------------|--------------| | 50 | ~9ms | ~4.6 MB | | 100 | ~16ms | ~6.1 MB | | 200 | ~23ms | ~15.8 MB |

Memory mỗi endpoint: ~0.03-0.04 MB

Benchmarks

File-based routing có zero runtime overhead vì:

  • Routes chỉ được scan và register một lần khi startup
  • Sau startup, routing sử dụng native high-performance router của Elysia
  • Không có thêm lookups hay file system operations trong quá trình xử lý requests

Insight quan trọng: Thời gian startup chỉ quan trọng khi start server. Khi đã chạy, hiệu năng hoàn toàn tương đương với việc register routes thủ công.

Chạy benchmarks tự mình:

bun run benchmark.ts           # Runtime performance
bun --expose-gc benchmark-memory.ts  # Memory footprint

Yêu cầu hệ thống

  • Bun v1.2.8 trở lên
  • Elysia ^1.3.4 trở lên

License

MIT

Tác giả

The Anh

Đóng góp

Mọi đóng góp đều được chào đón! Vui lòng tạo issue hoặc pull request trên GitHub.

Links