ikari
v0.9.8
Published
Elagant Web Framework for Bun
Downloads
36
Readme
Quick Start
Install
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:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Create first controller
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],
});
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 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.
@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.
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.
@Service({ transient: true })
class UserService
{
async list() {
// logic here
}
}
:warning: ikare use 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 thectx.params
object. If you want to access the value of a path parameter, you can usectx.params.id
orctx.param("id")
.
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.
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.
:information_source: Handler type is
Handler = (ctx: Context) => Context | Promise<Context> | void | Promise<void>
and you can find it inimport { Handler } from "ikari";
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"
- Example:
controllers
is used to define controllers for your application.- Example:
controllers: [UserController]
orcontrollers: [UserController, PostController]
- Example:
middlewares
is used to define middleware for your application.- Example:
middlewares: [authMiddleware, loggerMiddleware]
- Example:
errorHandler
is used to define a global error handler for your application.- Example:
errorHandler: (err: Errorlike) => Response | Promise<Response>
. It is replace with default error handler if not provided. If not provided default error handler will be used.
- Example:
disableStartupMessage
is used to disable startup message. Default value isfalse
.- Example:
disableStartupMessage: true
- Example:
serveOptions
is used to define bun serve options.- Example:
serveOptions: { port: 3000 }
. You can provide all bun serve options here.
- Example:
group
is used to define a group for your application. See Routing Groups for more information
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 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.
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
.
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.@Get("/users") async list(ctx: Context) { // GET /users?page=1 const page = ctx.query("page"); // page = "1" }
ctx.queries()
Returns all query parameters.@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.@Get("/users/:id") async detail(ctx: Context) { // GET /users/1 const id = ctx.param("id"); // id = "1" }
ctx.params
Returns all path parameters.@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 useContent-Type
header to parse request body.@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.@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.@Get("/users") async list(ctx: Context) { const token = ctx.cookie("token"); // token = "123456" }
ctx.set()
Sets the specified header to the Response object.@Get("/users") async list(ctx: Context) { ctx.set("X-Frame-Options", "DENY"); }
ctx.append()
Appends the specified value to the specified header.@Get("/users") async list(ctx: Context) { ctx.append("X-RateLimit-Limit", "1000"); }
ctx.status()
Sets the status code of the Response object.@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.@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.@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.@Get("/users") async list(ctx: Context) { ctx.redirect("/user/1"); ctx.redirect("/user/1", 301); }
ctx.url()
Returns the full URL of the request.@Get("/users") async list(ctx: Context) { const url = ctx.url(); }
ctx.next()
Calls the next handler in the chain.@Get("/users") async list(ctx: Context) { return ctx.next(); }
Response specific methods.
ctx.setCookie()
Sets the specified cookie to the Response object.@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.@Get("/users") async list(ctx: Context) { const userAgent = ctx.get("User-Agent"); }
ctx.getStatus()
Returns the status code of the Response object.@Get("/users") async list(ctx: Context) { const status = ctx.getStatus(); }
ctx.getResHeader()
Returns the value of the specified header from the Response object.@Get("/users") async list(ctx: Context) { const xFrameOptions = ctx.getResHeader("X-Frame-Options"); }
ctx.json()
Sets the JSON data to the Response object.@Get("/users") async list(ctx: Context) { ctx.json({ name: "John Doe" }); }
ctx.string()
Sets the string data to the Response object.@Get("/users") async list(ctx: Context) { ctx.string("Hello World"); }
ctx.buffer()
Sets the buffer data to the Response object.@Get("/users") async list(ctx: Context) { ctx.buffer(Buffer.from("Hello World")); }
ctx.file()
Returns the specified file as a response..@Get("/users") async list(ctx: Context) { ctx.file(Bun.file("file.txt")); }
ctx.raw()
Sets the specified Response object to the Context response.@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.@Get("/users") async list(ctx: Context) { ctx.locals.set("name", "John Doe"); }
ctx.locals.get()
Returns the value of the specified local variable.@Get("/users") async list(ctx: Context) { const name = ctx.locals.get("name"); }
ctx.locals.has()
Returns true if the specified local variable exists.@Get("/users") async list(ctx: Context) { const hasName = ctx.locals.has("name"); }
ctx.locals.delete()
Deletes the specified local variable.@Get("/users") async list(ctx: Context) { ctx.locals.delete("name"); }
ctx.locals.clear()
Clears all local variables.@Get("/users") async list(ctx: Context) { ctx.locals.clear(); }
ctx.locals.all()
Returns all local variables.@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 for more information.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.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.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.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] });