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

@ldystudio/better-elysia

v1.1.0

Published

An extended version of Elysia with decorators and more features

Readme

Better Elysia

Better Elysia is an npm package that enhances the already fun and lightweight Elysia.js framework by introducing powerful decorators to simplify and streamline your development process. With this package, you can enjoy a more expressive and organized way to define routes, middleware, and more, making your Elysia.js experience even more enjoyable and productive.

Installation

bun add better-elysia

IMPORTANT!

This package only works with bun runtime (Node and deno will not work)

Add these in your tsconfig.json

"emitDecoratorMetadata": true,
"experimentalDecorators": true,

To bootstrap the application

Bootstrap

import { ElysiaFactory, LoggerService } from 'better-elysia';
import { AppModule } from './app.module';

async function bootstrap() {
	const app = await ElysiaFactory.create(AppModule, {
		auth: () => {}, // ADD YOUR AUTH HANDLER HERE,
		response: () => {}, // ADD YOUR RESPONSE HANDLER HERE
		error: () => {}, // ADD YOUR ERROR HANDLER HERE
		cors: { origin: '*' }, // ADD YOUR CORS CONFIG HERE (it's optional if not passed application won't use cors)
		swagger: { provider: 'swagger-ui' }, // ADD YOUR SWAGGER CONFIG HERE (it's optional if not passed application won't use swagger)
		beforeStart: [], // ADD all functions you want to run before application starts (example connecting to database)
	});

	app.listen(5000, () => LoggerService.log('Application started at http://localhost:5000'));
}

bootstrap();

AppModule

Add your controllers here

import { Module } from 'better-elysia';
import { AuthController } from './modules/auth/auth.controller';
import { ChatWebsocket } from './modules/chat/chat.websocket';

// ADD YOUR CONTROLLERS HERE
@Module({ controllers: [AuthController, ChatWebsocket] })
export class AppModule {}

Controller

Example:

import { ApiTag, Controller, Get, Post } from 'better-elysia';

@ApiTag('Auth')
@Controller('/api/auth')
export class AuthController {
	@Post('/sign-in')
	async signinHandler() {}
}

To use validation you can use validator provided by elysia, here's how i do this

Create a schema file auth.schema.ts

Example:

import { t } from 'better-elysia';

export namespace AuthSchema {
	export const Signin = t.Object({
		email: t.String({ format: 'email' }),
		password: t.String({ minLength: 8, maxLength: 32 }),
	});
	export type Signin = typeof Signin.static;
}

and use it in the controller

Example:

import { ApiTag, Body, Controller, Post } from 'better-elysia';
import { AuthSchema } from './auth.schema';

@ApiTag('Auth')
@Controller('/api/auth')
export class AuthController {
	@Post('/sign-in')
	async signinHandler(@Body(AuthSchema.Signin) body: AuthSchema.Signin) {}
}

Same works for query too

Example:

import { ApiTag, Controller, Post, Query } from 'better-elysia';
import { AuthSchema } from './auth.schema';

@ApiTag('Auth')
@Controller('/api/auth')
export class AuthController {
	@Post('/sign-in')
	async signinHandler(@Query(AuthSchema.Signin) query: AuthSchema.Signin) {}
}

For params

Example:

import { ApiTag, Controller, Get, Param } from 'better-elysia';

@ApiTag('Auth')
@Controller('/api/auth')
export class AuthController {
	@Get('/:id')
	async getById(@Param('id') id: string) {
		console.log(id);
	}
}

Streaming

For streaming use GeneratorFunctions

Example:

This will stream numbers from 0 to 9999

import { ApiTag, Controller, Get } from 'better-elysia';

@ApiTag('Auth')
@Controller('/api/auth')
export class AuthController {
	@Get('/stream-test')
	async *streamHandler() {
		for (let i = 0; i < 10000; i++) yield i;
	}
}

Public

To make endpoint public and not use auth middleware passed in ElysiaFactory use @Public Decorator

Example:

import { ApiTag, Controller, Get, Public } from 'better-elysia';

@ApiTag('Auth')
@Controller('/api/auth')
export class AuthController {
	@Public()
	@Get()
	async publicEndpoint() {}
}

Websocket

For implementation of websocket use @Websocket decorator

Example:

import { Close, Message, Open, t, Websocket, LoggerService, type WS } from 'better-elysia';

const MessageSchema = t.Object({
	content: t.String(),
});
type MessageSchema = typeof MessageSchema.static;

// TO MAKE WEBSOCKET PUBLIC USE @Websocket('/chat', { public: true })
@Websocket('/chat', { public: false })
export class ChatWebsocket {
    private readonly logger = LoggerService(ChatWebsocket.name);

	@Open()
	async openHandler(ws: WS) {
		// FUNCTION WILL RUN AFTER CONNECTING TO Websocket
	}

	@Close()
	async closeHandler(ws: WS) {
		// FUNCTION WILL RUN AFTER DISCONNECTING FROM Websocket
	}

	@Message(MessageSchema)
	async messageHandler(ws: WS, msg: MessageSchema) {
		// FUNCTION WILL RUN AFTER RECEIVING A MESSAGE
	}
}

Service

For making services use @Service decorator what it will do is make a singleton of the class and inject the singleton to all the @Controller and @Websocket

Example:

import { Service } from 'better-elysia';

@Service()
export class UserService {
	async findOneById(id: number) {
		return { id, name: 'Ateeb' };
	}
}

Now use it anywhere

Example:

import { ApiTag, Controller, Get } from 'better-elysia';
import { UserService } from './auth.service';

@ApiTag('Auth')
@Controller('/api/auth')
export class AuthController {
	constructor(private readonly userService: UserService) {}

    @Get('/me')
	async me() {
		return this.userService.findOneById(1);
	}
}
import { Close, Message, Open, t, Websocket, type WS } from 'better-elysia';
import { UserService } from '../auth/auth.service';

const MessageSchema = t.Object({
	content: t.String(),
});
type MessageSchema = typeof MessageSchema.static;

// TO MAKE WEBSOCKET PUBLIC USE @Websocket('/chat', { public: true })
@Websocket('/chat', { public: false })
export class ChatWebsocket {
	constructor(private readonly userService: UserService) {}

	@Open()
	async openHandler(ws: WS) {
		// FUNCTION WILL RUN AFTER CONNECTING TO Websocket
	}

	@Close()
	async closeHandler(ws: WS) {
		// FUNCTION WILL RUN AFTER DISCONNECTING FROM Websocket
	}

	@Message(MessageSchema)
	async messageHandler(ws: WS, msg: MessageSchema) {
		// FUNCTION WILL RUN AFTER RECEIVING A MESSAGE
	}
}

Custom Decorator

To make your own custom parameter decorator use createCustomParameterDecorator function

UseCase and Example:

import { createCustomParameterDecorator, type Handler } from 'better-elysia';

export const AuthHandler: Handler = (c) => {
	// ADD YOUR AUTH LOGIC HERE
	(c.store as any).user = 1;
};

export const CurrentUser = () => {
	return createCustomParameterDecorator((c) => (c.store as any).user);
};

Add you AuthHandler in ElysiaFactory

Example:

import { ElysiaFactory, LoggerService } from 'better-elysia';
import { AppModule } from './app.module';
import { AuthHandler } from './middleware/auth.middleware';

async function bootstrap() {
	const app = await ElysiaFactory.create(AppModule, {
		auth: AuthHandler,
	});

	app.listen(5000, () => LoggerService.log('Application started at http://localhost:5000'));
}

bootstrap();

and the custom decorator you just create in Controller

Example:

import { ApiTag, Controller, Get } from 'better-elysia';
import { UserService } from './auth.service';
import { CurrentUser } from '../../middleware/auth.middleware';

@ApiTag('Auth')
@Controller('/api/auth')
export class AuthController {
	constructor(private readonly userService: UserService) {}

	@Get('/me')
	async me(@CurrentUser() userId: number) {
		return this.userService.findOneById(userId);
	}
}

License

This project is licensed under the MIT License