Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ikari-js/ikari
Elagant Web Framework for Bun
https://github.com/ikari-js/ikari
bun decorators elegant fast framework http server web
Last synced: about 2 months ago
JSON representation
Elagant Web Framework for Bun
- Host: GitHub
- URL: https://github.com/ikari-js/ikari
- Owner: ikari-js
- License: mit
- Created: 2023-10-10T17:56:36.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-28T16:35:37.000Z (about 2 months ago)
- Last Synced: 2024-10-31T09:41:52.668Z (about 2 months ago)
- Topics: bun, decorators, elegant, fast, framework, http, server, web
- Language: TypeScript
- Homepage:
- Size: 418 KB
- Stars: 4
- Watchers: 1
- Forks: 1
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
ikari
Elagant Web Framework for Bun.
![npm](https://img.shields.io/npm/v/ikari) ![npm](https://img.shields.io/npm/dt/ikari) ![GitHub](https://img.shields.io/github/license/ikari-js/ikari) ![GitHub stars](https://img.shields.io/github/stars/ikari-js/ikari) ![GitHub last commit](https://img.shields.io/github/last-commit/ikari-js/ikari)
## Quick Start
### Install
```shell
bun add ikari
```### TypeScript Configuration
> :warning: ikari requires TypeScript experimental decorators features to be enabled. To enable these features, add the following configuration to your `tsconfig.json` file:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```### Create first controller
```typescript
import { Context, Serve } from "ikari";
import { Controller, Get } from "ikari/decorators";@Controller("/")
class IkariController {
@Get("/")
async index(ctx: Context) {
return ctx.string("Hello Ikari");
}
}Serve({
controllers: [IkariController],
});```
Table of Contents
- About The Project
- Motivation
- Controller
- Service and Inject Decorators
- GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD
- ALL
- Middleware
- Configuration
- Routing Groups
- Server
- Context
- Official Middlewares
- Contributors
## About The Project
Welcome to ikari, a powerful TypeScript-based HTTP framework meticulously designed for elegant and enterprise-level applications. Born with a focus on providing a seamless experience for [bun](https://github.com/oven-sh/bun) runtime, ikari leverages TypeScript's strong typing capabilities to ensure robustness and maintainability in your web applications.## Motivation
ikari is built with a straightforward vision: TypeScript decorators. This choice is aimed at creating an enterprise-ready framework with simplicity at its core. TypeScript decorators provide an organized and scalable way to structure code, simplifying feature implementation and ensuring adaptability to changing project needs. This approach prioritizes a developer-friendly experience, enhancing code readability and speeding up development. In essence, ikari embraces TypeScript decorators to make web development simple, scalable, and enjoyable.## Decorators
ikari provides a set of decorators to help you build your web application. These decorators are designed to be simple and intuitive, allowing you to focus on your application's logic.
### Controller
The `Controller` decorator is used to define a controller class. This decorator takes a path as its first argument, which will be used as the base path for all routes defined in the controller.
```typescript
@Controller("/users")
class UserController {}
```### Service and Inject Decorators
ikari provides a built-in dependency injection system to help you manage your services. You can use the `Service` decorator to define a service class and the `Inject` decorator to inject services into your controllers or other services.
```typescript
import { Service, Inject, Controller } from "ikari/decorators";
import { Context, Serve } from "ikari";@Service()
class UserService
{
async list() {
// logic here
}
}@Controller("/users")
class UserController {
@Inject()
userService: UserService;@Get("/list")
async list(ctx: Context) {
this.userService.list();
}
}Serve({
controllers: [UserController],
});
```Services are by default singleton instances, meaning that the same instance will be shared across all controllers and services that inject it. If you want to create a new instance of a service for each injection, you can use the `transient` option with the `Service` decorator.
```typescript
@Service({ transient: true })
class UserService
{
async list() {
// logic here
}
}
```> :warning: ikare use [typedi](https://github.com/typestack/typedi) for dependency injection under the hood. You can use all typedi options with ikari.
### GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD
These decorators are used to define routes in a controller. They take a path as their first argument, which will be appended to the base path of the controller. The path can also contain parameters. If path argument are not provided function name will be used as path.
> :warning: Path parameters like `/users/:id` will be accessible in the `ctx.params` object. If you want to access the value of a path parameter, you can use `ctx.params.id` or `ctx.param("id")`.
```typescript
import { Context, Serve } from "ikari";
import { Controller, Get, Post, Put, Patch, Delete, Options, Head } from "ikari/decorators";@Controller("/users")
class UserController {
@Get("/list")
async list(ctx: Context) {
// logic here
}@Get("/detail/:id")
async detail(ctx: Context) {
// :id will be replaced with the value of id in ctx.params.id or ctx.param("id")
// logic here
}@Post("/create")
async create(ctx: Context) {
// logic here
}@Put("/update")
async update(ctx: Context) {
// logic here
}@Patch("/patch")
async patch(ctx: Context) {
// logic here
}@Delete("/delete")
async delete(ctx: Context) {
// logic here
}@Options("/options")
async options(ctx: Context) {
// logic here
}@Head("/head")
async head(ctx: Context) {
// logic here
}
}Serve({
controllers: [UserController],
});
```### ALL
The `ALL` decorator is used to define a route that matches all HTTP methods. It takes a path as its first argument, which will be appended to the base path of the controller. The path can also contain parameters. If path argument are not provided function name will be used as path.
```typescript
import { Context, Serve } from "ikari";
import { Controller, All } from "ikari/decorators";@Controller("/users")
class UserController {
@All("/list")
async list(ctx: Context) {
// logic here
}
}Serve({
controllers: [UserController],
});
```### Middleware
ikari provides a `Before` and `After` decorator to define middleware for routes. These decorators take a `Handler` type functions array as their first argument. The middleware will be executed in the order they are defined. For controller level middleware see [Configuration](#configuration).
> :information_source: Handler type is `Handler = (ctx: Context) => Context | Promise | void | Promise` and you can find it in `import { Handler } from "ikari";`
```typescript
import { Context, Serve } from "ikari";
import { Controller, Get, Before, After } from "ikari/decorators";@Controller("/users")
function authMiddleware(ctx: Context) {
// logic here
return ctx.next();
}function loggerMiddleware(ctx: Context) {
// logic here
return ctx.next();
}class UserController {
@Get("/list")
@Before([authMiddleware])
@After([loggerMiddleware])
async list(ctx: Context) {
// logic here
}
}Serve({
controllers: [UserController],
});
```## Configuration
ikari provides a `Config` type to define configuration for your application. This type is used in `Serve` function.
- `prefix` is used to define a prefix for all routes in your application. Default value is empty string.
- Example: `prefix: "/api"`
- `controllers` is used to define controllers for your application.
- Example: `controllers: [UserController]` or `controllers: [UserController, PostController]`
- `middlewares` is used to define middleware for your application.
- Example: `middlewares: [authMiddleware, loggerMiddleware]`
- `errorHandler` is used to define a global error handler for your application.
- Example: `errorHandler: (err: Errorlike) => Response | Promise`. It is replace with default error handler if not provided. If not provided default error handler will be used.
- `disableStartupMessage` is used to disable startup message. Default value is `false`.
- Example: `disableStartupMessage: true`
- `serveOptions` is used to define bun serve options.
- Example: `serveOptions: { port: 3000 }`. You can provide all bun serve options here.
- `group` is used to define a group for your application. See [Routing Groups](#routing-groups) for more information```typescript
import { Config, Serve } from "ikari";
import { UserController } from "./controllers/user.controller";
import { authMiddleware, loggerMiddleware } from "./middlewares";const config: Config = {
prefix: "/api",
controllers: [UserController],
middlewares: [authMiddleware, loggerMiddleware],
errorHandler: (err) => {
// logic here
},
disableStartupMessage: true,
serveOptions: {
port: 3000,
},
};Serve(config);
```## Routing Groups
ikari [config](#configuration) provides a `group` property to define a group for your application. This property is used to define a prefix for all routes in your application. You can also define a middleware for your group. This middleware will be executed before all routes in your group.
```typescript
import { Config, Serve } from "ikari";
import { UserController } from "./controllers/user.controller";
import { PaymentController } from "./controllers/payment.controller";
import { authMiddleware, loggerMiddleware } from "./middlewares";const config: Config = {
prefix: "/api",
middlewares: [authMiddleware],
group: [
{
prefix: "/users",
controllers: [UserController],
},
{
prefix: "/payments",
middlewares: [loggerMiddleware],
controllers: [PaymentController],
},
]
};Serve(config);
```## Server
ikari return a `Server` object when you call `Serve` function. This object has a `server` property that is a bun server instance. You can use this instance to access bun server methods and properties. Such as `server.stop()` or `server.port`.
```typescript
import { Config, Serve } from "ikari";
import { UserController } from "./controllers/user.controller";
import { authMiddleware, loggerMiddleware } from "./middlewares";const config: Config = {
prefix: "/api",
middlewares: [authMiddleware],
group: [
{
prefix: "/users",
controllers: [UserController],
},
]
};const server = Serve(config);
server.stop(); // stop the server
```## Context
ikari provides a `Context` object to access ikari context methods and properties. Such as `ctx.params` or `ctx.body()`. You can also use `ctx.next()` to call next middleware or route handler.
- Request specific methods.
- `ctx.query()` Returns the value of the specified query parameter.
Example```typescript
@Get("/users")
async list(ctx: Context) {
// GET /users?page=1
const page = ctx.query("page"); // page = "1"
}
```
- `ctx.queries()` Returns all query parameters.
Example```typescript
@Get("/users")
async list(ctx: Context) {
// GET /users?page=1&limit=10
const queries = ctx.queries(); // queries = { page: "1", limit: "10" }
}
```
- `ctx.param()` Returns the value of the specified path parameter.
Example```typescript
@Get("/users/:id")
async detail(ctx: Context) {
// GET /users/1
const id = ctx.param("id"); // id = "1"
}
```
- `ctx.params` Returns all path parameters.
Example```typescript
@Get("/users/:id/:name")
async detail(ctx: Context) {
// GET /users/1/john-doe
const params = ctx.params; // params = { id: "1", name: "john-doe" }
}
```
- `ctx.body()` Returns the parsed body of the request. It use `Content-Type` header to parse request body.
Example```typescript
@Post("/users")
async create(ctx: Context) {
// curl -X POST -H "Content-Type: application/json" -d '{"name": "John Doe"}' http://localhost:3000/users
const body = await ctx.body(); // body = { name: "John Doe" }
}
```
- `ctx.formFile()` Returns the file of the specified form field.
Example```typescript
@Post("/users")
async create(ctx: Context) {
// curl -X POST -F "file=@/path/to/file" http://localhost:3000/users
const file = await ctx.formFile("file");
}
```- `ctx.cookie()` Returns the value of the specified cookie.
Example```typescript
@Get("/users")
async list(ctx: Context) {
const token = ctx.cookie("token"); // token = "123456"
}
```
- `ctx.set()` Sets the specified header to the Response object.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.set("X-Frame-Options", "DENY");
}
```
- `ctx.append()` Appends the specified value to the specified header.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.append("X-RateLimit-Limit", "1000");
}
```
- `ctx.status()` Sets the status code of the Response object.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.status(200);
}
```
- `ctx.ip()` Returns the client's IP address. By default it will return remote-addr, if ipHeader is specified it will return the value of the specified header.
Example
```typescript
@Get("/users")
async list(ctx: Context) {
const ip = ctx.ip(); // remote-addr
const xForwardedFor = ctx.ip("x-forwarded-for"); // x-forwarded-for
}
```
- `ctx.authorization()` Returns the value of the Authorization header.
Example
```typescript
@Get("/users")
async list(ctx: Context) {
const authorization = ctx.authorization();
}
```
- `ctx.redirect()` Redirects to the specified URL with the specified status code. If the status code is not specified it will default to 302.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.redirect("/user/1");
ctx.redirect("/user/1", 301);
}
```
- `ctx.url()` Returns the full URL of the request.
Example```typescript
@Get("/users")
async list(ctx: Context) {
const url = ctx.url();
}
```
- `ctx.next()` Calls the next handler in the chain.
Example```typescript
@Get("/users")
async list(ctx: Context) {
return ctx.next();
}
```
- Response specific methods.
- `ctx.setCookie()` Sets the specified cookie to the Response object.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.setCookie("token", { value: "123456", httpOnly: true, expires: new Date("2021-12-31") });
}
```
- `ctx.get()` Returns the value of the specified header from the Request object.
Example```typescript
@Get("/users")
async list(ctx: Context) {
const userAgent = ctx.get("User-Agent");
}
```
- `ctx.getStatus()` Returns the status code of the Response object.
Example```typescript
@Get("/users")
async list(ctx: Context) {
const status = ctx.getStatus();
}
```
- `ctx.getResHeader()` Returns the value of the specified header from the Response object.
Example```typescript
@Get("/users")
async list(ctx: Context) {
const xFrameOptions = ctx.getResHeader("X-Frame-Options");
}
```
- `ctx.json()` Sets the JSON data to the Response object.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.json({ name: "John Doe" });
}
```
- `ctx.string()` Sets the string data to the Response object.
Example
```typescript
@Get("/users")
async list(ctx: Context) {
ctx.string("Hello World");
}
```
- `ctx.buffer()` Sets the buffer data to the Response object.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.buffer(Buffer.from("Hello World"));
}
```
- `ctx.file()` Returns the specified file as a response..
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.file(Bun.file("file.txt"));
}
```
- `ctx.raw()` Sets the specified Response object to the Context response.
Example```typescript
@Get("/users")
async list(ctx: Context) {
const response = new Response();
ctx.raw(response);
}
```
- Local variables.
- `ctx.locals.set()` Sets the value of the specified local variable.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.locals.set("name", "John Doe");
}
```
- `ctx.locals.get()` Returns the value of the specified local variable.
Example```typescript
@Get("/users")
async list(ctx: Context) {
const name = ctx.locals.get("name");
}
```
- `ctx.locals.has()` Returns true if the specified local variable exists.
Example```typescript
@Get("/users")
async list(ctx: Context) {
const hasName = ctx.locals.has("name");
}
```
- `ctx.locals.delete()` Deletes the specified local variable.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.locals.delete("name");
}
```
- `ctx.locals.clear()` Clears all local variables.
Example```typescript
@Get("/users")
async list(ctx: Context) {
ctx.locals.clear();
}
```
- `ctx.locals.all()` Returns all local variables.
Example```typescript
@Get("/users")
async list(ctx: Context) {
const locals = ctx.locals.all();
}
```
## Official Middlewares
ikari provides a set of official middlewares to help you build your web application. These middlewares are designed to be simple and intuitive, allowing you to focus on your application's logic.
- `CORS` middleware is used to enable CORS with various options. See [cors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for more information.
Example```typescript
import { Context, Serve } from "ikari";
import { Controller, Get } from "ikari/decorators";
import { CORS } from "ikari/middlewares/cors";@Controller("/users")
class UserController {
@Get("/")
async list(ctx: Context) {
// logic here
}
}Serve({
middlewares: [CORS()],
controllers: [UserController]
});
```
- `helmet` middleware is used to set various HTTP headers to help protect your application.
Example```typescript
import { Context, Serve } from "ikari";
import { Controller, Get } from "ikari/decorators";
import { helmet } from "ikari/middlewares/helmet";@Controller("/users")
class UserController {
@Get("/")
async list(ctx: Context) {
// logic here
}
}Serve({
middlewares: [helmet()],
controllers: [UserController]
});
```
- `RequestId` middleware is used to set a unique ID for each request.
Example```typescript
import { Context, Serve } from "ikari";
import { Controller, Get } from "ikari/decorators";
import { RequestId } from "ikari/middlewares/request-id";@Controller("/users")
class UserController {
@Get("/")
async list(ctx: Context) {
// logic here
}
}Serve({
middlewares: [RequestId()],
controllers: [UserController]
});
```
- `logger` middleware is used to log request and response information.
Example```typescript
import { Context, Serve } from "ikari";
import { Controller, Get } from "ikari/decorators";
import { Logger } from "ikari/middlewares/logger";@Controller("/users")
class UserController {
@Get("/")
async list(ctx: Context) {
// logic here
}
}Serve({
middlewares: [Logger()],
controllers: [UserController]
});
```