0.8.1 • Published 1 month ago

ikari v0.8.1

Weekly downloads
-
License
MIT
Repository
github
Last release
1 month ago

npm npm GitHub GitHub stars GitHub last commit

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 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").

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 in import { 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"
  • 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<Response>. 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
  • strict is used to enable strict trailing slash handling for routes. Default value is false.
    • Example: strict: true. If you enable strict mode, /users and /users/ will be different routes.
  • 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 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,
  strict: false,
  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 use Content-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.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.stream() Sets the stream data to the Response object.

      @Get("/users")
      async list(ctx: Context) {
        ctx.stream(fs.createReadStream("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]
    });

Contributors

0.8.1

1 month ago

0.8.0

1 month ago

0.7.2

5 months ago

0.7.3

5 months ago

0.7.1

5 months ago

0.7.0

5 months ago

0.6.0

5 months ago

0.5.7

5 months ago

0.5.6

5 months ago

0.5.4

6 months ago

0.5.3

6 months ago

0.5.5

6 months ago

0.5.2

6 months ago

0.5.1

6 months ago

0.5.0

6 months ago

0.4.0

6 months ago

0.3.1

6 months ago

0.3.0

6 months ago

0.2.0-alpha.10

6 months ago

0.2.0-alpha.9

6 months ago

0.2.0-alpha.8

6 months ago

0.2.0-alpha.7

6 months ago

0.2.0-alpha.6

6 months ago

0.2.0-alpha.5

6 months ago

0.2.0-alpha.4

6 months ago

0.2.0-alpha.3

6 months ago

0.2.0-alpha.2

6 months ago

0.2.0-alpha.1

6 months ago

0.2.0

6 months ago

0.1.0

6 months ago

0.0.11

6 months ago

0.0.10

6 months ago

0.0.9

6 months ago

0.0.8

6 months ago

0.0.7

6 months ago

0.0.6

6 months ago

0.0.5

6 months ago

0.0.4

6 months ago

0.0.3

6 months ago

0.0.2

6 months ago

0.0.1

6 months ago