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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@byteholic/nelysia

v0.1.0

Published

Native Elysia modular framework

Readme

Nelysia

NestJS-style modular framework built natively on ElysiaJS + Bun.

npm version bun license


Features

  • 🧩 @Plugin — NestJS-style @Module powered by native Elysia .use()
  • 💉 @Service + @Inject — Lightweight DI container
  • 🌐 @Controller + HTTP verbs@Get, @Post, @Put, @Patch, @Delete
  • 🔌 @WsController + @Ws — Native WebSocket support with pub/sub
  • 🎯 Context decorators@Body, @Query, @Params, @Path, @Headers, @Cookie
  • 🛡️ @BeforeHandle — Per-route guards
  • 📋 @Schema — Elysia t validation per route
  • 📖 @Detail — OpenAPI / Swagger metadata
  • ⚙️ @Macro — Elysia .macro() as class-based decorators
  • 🔁 Lifecycle hooksonRequest, onError, onAfterResponse, etc. in @Plugin

Install

bun add @byteholic/nelysia elysia
bun add -d bun-types

tsconfig.json requirements

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "strict": true
  }
}

Quick Start

// main.ts
import { Elysia } from "elysia";
import { buildPlugin } from "@byteholic/nelysia";
import { AppPlugin } from "./app.plugin";

new Elysia()
  .use(buildPlugin(AppPlugin))
  .listen(3000);

Core Concepts

@Service — Injectable class

Register a class in the DI container. Pass deps as an explicit array.

import { Service } from "@byteholic/nelysia";

@Service()
export class UserRepo {
  private users = [{ id: 1, name: "Alice" }];
  findAll() { return this.users; }
}

@Service([UserRepo])
export class UsersService {
  constructor(private repo: UserRepo) {}
  all() { return this.repo.findAll(); }
}

@Inject — Per-parameter injection

Use on constructor parameters instead of (or alongside) @Service([]).

import { Inject, Controller, Get } from "@byteholic/nelysia";

@Controller("/users")
export class UsersController {
  constructor(@Inject(UsersService) private svc: UsersService) {}

  @Get("/")
  getAll() { return this.svc.all(); }
}

Both styles are equivalent:

// Style A — class-level deps
@Service([UsersService])
@Controller("/users")
export class UsersController {
  constructor(private svc: UsersService) {}
}

// Style B — per-param @Inject
@Controller("/users")
export class UsersController {
  constructor(@Inject(UsersService) private svc: UsersService) {}
}

@Plugin — Feature module

Composes services, controllers, sub-plugins, guards and hooks.

import { Plugin } from "@byteholic/nelysia";

@Plugin({
  name:        "users",
  imports:     [OtherPlugin],          // sub-plugins (shared DI)
  services:    [UserRepo, UsersService],
  controllers: [UsersController],
  wsControllers: [UsersWsController],  // WebSocket controllers
  guard: {
    beforeHandle: requireAuth,         // applied to ALL routes
  },
  hooks: {
    onRequest: ({ request }) =>
      console.log(request.method, request.url),
    onError: ({ error }) =>
      console.error(error.message),
  },
})
export class UsersPlugin {}

Then in main.ts:

new Elysia().use(buildPlugin(UsersPlugin)).listen(3000);

HTTP Routes

import { t } from "elysia";
import {
  Controller, Get, Post, Delete,
  Schema, BeforeHandle, Detail,
  Body, Path, Query, Set,
} from "@byteholic/nelysia";

const adminOnly = ({ headers, status }: any) => {
  if (headers["x-role"] !== "admin") return status(403);
};

@Controller("/users")
export class UsersController {
  constructor(@Inject(UsersService) private svc: UsersService) {}

  @Detail({ tags: ["Users"], summary: "List users" })
  @Schema({ query: t.Object({ page: t.Optional(t.Numeric({ default: 1 })) }) })
  @Get("/")
  getAll(@Query() query: { page?: number }) {
    return this.svc.all();
  }

  @Detail({ tags: ["Users"], summary: "Get user by id" })
  @Schema({ params: t.Object({ id: t.Numeric() }) })
  @Get("/:id")
  getOne(@Path("id") id: number, @Set() set: any) {
    const user = this.svc.byId(id);
    if (!user) { set.status = 404; return { error: "Not found" }; }
    return user;
  }

  @Detail({ tags: ["Users"], summary: "Create user" })
  @Schema({ body: t.Object({ name: t.String({ minLength: 1 }) }) })
  @Post("/")
  create(@Body() body: { name: string }, @Set() set: any) {
    set.status = 201;
    return this.svc.create(body);
  }

  @BeforeHandle(adminOnly)
  @Schema({ params: t.Object({ id: t.Numeric() }) })
  @Delete("/:id")
  remove(@Path("id") id: number) {
    return this.svc.remove(id);
  }
}

WebSocket

import { t } from "elysia";
import { WsController, Ws, WsSchema, WsHandlers, Inject } from "@byteholic/nelysia";

@WsController()
export class ChatWsController {
  constructor(@Inject(ChatService) private chat: ChatService) {}

  @WsSchema({
    body:   t.Object({ user: t.String(), text: t.String() }),
    params: t.Object({ room: t.String() }),
  })
  @Ws("/chat/:room")
  chat(): WsHandlers {
    const svc = this.chat;
    return {
      open(ws) {
        ws.subscribe(ws.data.params.room);
        ws.send("Welcome!");
      },
      message(ws, body) {
        const msg = svc.format(body.user, body.text, ws.data.params.room);
        svc.save(msg);
        ws.publish(ws.data.params.room, JSON.stringify(msg));
      },
      close(ws) {
        ws.unsubscribe(ws.data.params.room);
      },
    };
  }
}

// Register in @Plugin:
@Plugin({
  name:          "chat",
  services:      [ChatService],
  wsControllers: [ChatWsController],
})
export class ChatPlugin {}

Connect with:

bun add -g wscat
wscat -c "ws://localhost:3000/chat/general"
# send: {"user":"Alice","text":"Hello!"}

Context Decorators

| Decorator | Extracts | |---|---| | @Ctx() | Full Elysia context | | @Body() | ctx.body | | @Query() | ctx.query (all) | | @Params() | ctx.params (all) | | @Headers() | ctx.headers | | @Cookie() | ctx.cookie | | @Set() | ctx.set (status/headers) | | @Path("id") | ctx.params.id (single) | | @Q("page") | ctx.query.page (single) |


Lifecycle Hooks

Available in hooks: inside @Plugin:

| Hook | Fires when | |---|---| | onRequest | Every incoming request | | onParse | Body parsing | | onTransform | Before validation | | onBeforeHandle | Before route handler | | onAfterHandle | After route handler | | onAfterResponse | After response is sent | | onError | On any error | | mapResponse | Transform response before send |


Macro

Wraps Elysia's native .macro() API:

import { Macro, MacroHandler } from "@byteholic/nelysia";

@Macro({ name: "auth" })
export class AuthMacro {
  @MacroHandler("roles")
  roles(required: string[]) {
    return {
      beforeHandle({ cookie, status }: any) {
        if (!required.includes(cookie.session?.value?.role))
          return status(403);
      },
    };
  }
}

// Register in @Plugin:
@Plugin({ macros: [AuthMacro], controllers: [AdminController] })
export class AdminPlugin {}

Full Decorator Reference

DI

| Decorator | Description | |---|---| | @Service(deps?) | Register class in DI container | | @Inject(Token) | Per-parameter dependency injection |

Plugin

| Decorator / Function | Description | |---|---| | @Plugin(meta) | Define a feature module | | buildPlugin(MyPlugin) | Convert to native Elysia instance |

HTTP

| Decorator | Description | |---|---| | @Controller(prefix) | HTTP controller class | | @Get / @Post / @Put / @Patch / @Delete / @Options / @Head / @All | Route method | | @Schema({ body, query, params, headers, response }) | Elysia t validation | | @BeforeHandle(fn) | Per-route guard / hook | | @AfterHandle(fn) | Per-route after hook | | @OnError(fn) | Per-route error handler | | @Detail({ tags, summary }) | OpenAPI metadata |

WebSocket

| Decorator | Description | |---|---| | @WsController(prefix?) | WebSocket controller class | | @Ws(path?) | WebSocket endpoint (returns WsHandlers) | | @WsSchema({ body, query, params }) | WS message validation |


Project Structure

src/
  core/
    container.ts   — DI Container
    service.ts     — @Service, @Inject
    controller.ts  — @Controller, @Get, @Post, @Schema ...
    context.ts     — @Body, @Query, @Path, @Headers ...
    websocket.ts   — @WsController, @Ws, @WsSchema
    plugin.ts      — @Plugin, buildPlugin()
    macro.ts       — @Macro, @MacroHandler
  index.ts         — Public API barrel

With Swagger

bun add @elysiajs/swagger
import { swagger } from "@elysiajs/swagger";

new Elysia()
  .use(swagger({ documentation: { info: { title: "My API", version: "1.0.0" } } }))
  .use(buildPlugin(AppPlugin))
  .listen(3000);

// Docs at: http://localhost:3000/swagger

Requirements

  • Bun ≥ 1.0
  • ElysiaJS ≥ 1.0
  • "experimentalDecorators": true in tsconfig.json

License

MIT © byteholic