Skip to content

kurai-foundation/express-router

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kurai Foundation Express Router

Layer-2 framework based on express, which makes it faster and easier to build fault-tolerant backend applications

Table of contents

Installation

There is little to describe, the installation process is no different from any other package:

npm add @kurai-io/express-router

Or use any other package manager, the command is just an example, please consult your package manager's documentation for the exact installation command.

pnpm[yarn, etc...] install @kurai-io/express-router

Basic usage

Usage example

Let's create a simple application that can create and return users from the database
while allowing access only to authorized users

First, let's create schemas to validate the content that will go into the handlers

// src/schemas.ts

import { ISchema } from "@kurai-io/express-router"

export const UserCreateSchema: ISchema = {
  body: Joi.object({
    login: Joi.string().min(4).max(24).required(),
    password: Joi.string.min(8).max(128).required()
  })
}

export const UserGetSchema: ISchema = {
  params: Joi.object({
    id: Joi.string().length(24).required()
  })
}

Now let's write the code of the application itself

// src/app.ts

import { Application } from "@kurai-io/express-router"
import { Unauthorized } from "@kurai-io/express-router/exceptions"
import { UserCreateSchema, UserGetSchema } from "./schemas.ts"

const app = new Application()
const builder = app.createRoute({
  root: "/users",
  middleware: request => {
    let autenticated = false

    // Verify that the user has permission to execute requests
    if (request.header("access-token") === process.env.ACCESS_TOKEN) authenticated = true
    
    return { authenticated }
  }
})

// The request argument already contains the "authenticated" field (and is typed)
builder.schema(UserCreateSchema).post("/create", async request => {
  // Here you can either throw or return an exception
  if (!request.authenticated) return Unauthorized("Invalid token")

  // We recommend returning exceptions because it creates 
  // more predictable code than the "throw" syntax. 
  // But the choice is yours, you can use both approaches

  // Insert the user into the database
  // No need for additional validation, because the schema did all the work for us.
  const insertionResult = await database.insertOne({ ...request.body })
  
  return insertionResult.insertedId
})

// Define a GET request to retrieve the user
builder.schema(UserGetSchema).get("/:id", async request => {
  // No need for try/catch or other monstrosities, the router does all the work
  return database.findOne({ _id: new ObjectId(request.params.id) })
})

In the code above, we created several request handlers, but each of them either returned a response (the default response code is 200) or an error. Now let's create a handler that will return code 204 if successful and a custom error if not.

import { Exception } from "@kurai-io/express-router/exceptions"
import { CustomResponse } from "@kurai-io/express-router"

// ...

// Pass null to the schema to disable validation
builder.schema(null).head("/:id", async request => {
  const hasDocument = database.countDocuments({ _id: new ObjectId(request.params.id) })

  if (!hasDocument) {
    // We could use the NotFound error as we did earlier with Unauthorized, 
    // but instead we will create our own error, just for the sake of example. 
    // In most cases we should use predefined errors, as they are predefined for 
    // almost every 4xx and 5xx error code
    
    return new Exception({
      code: 404,
      name: "NotFound",
      message: "Document not found"
    })
  }

  // If the document exists, return 204. Since the response structure is slightly different 
  // for 4xx/5xx and 1xx-3xx responses, do not use exceptions to return the actual content
  return CustomResponse({
    code: 204,
    content: true
  })
})

Here we have created a simple and fail-safe application that knows how to validate, create and find users. It's not hard, right?

Basic swagger integration

To enable Swagger documentation, the following packages must be installed:

  • @kurai-io/express-router-swagger - Swagger transformer
  • swagger-ui-express - Swagger UI renderer

Then, in the application configuration, enable integration

const app = new Application({
  enableSwagger: true
})

Optionally, you can customize the display of documentation using the swagger field in the configuration

The documentation will be generated automatically from schemes and registered routes, the names will be set to automatically generated strings.

If desired, the route can be set the exception list and name manually, let's take the code from the last section as an example. We know that this handler can return a 404 error and a 204 response, let's set the metadata to reflect this

import { ResponseFactory } from "@kurai-io/express-router"

builder.schema(null).head("/:id", async request => {
  // ... see previous examle
}).metadata({
  responses: [ResponseFactory(204), NotFound],
  description: "Get user by ID"
})

The 500 Internal server error and the 200 Success response are added automatically.

The 200 Success response will not be added if other 1xx - 3xx responses have been added.

Advanced swagger integration

If desired, it is possible to create routes in such a way that all required answers are automatically displayed in the documentation. To do this, you need to create answer objects within the route itself:

// ...

builder.schema(null).get("/content", (request, responses) => {
  //                              content is required
  //                                       v
  return new responses.SuccessResponse(Math.random())
}, {
  // content filed omitted
  SuccessResponse: {
    code: 200,
    example: 0.1391314135135
  }
})

This approach will automatically add answers to the documentation and get rid of unnecessary variables in the models

Advanced API

Router builder

RouterBuilder is the main part of the framework that does all the work. In most cases the Application class will provide sufficient functionality, but if it fails, you can manually use RouterBuilder to achieve your tasks

import { RouterBuilder } from "@kurai-io/express-router"

const someRouter = new RouterBuilder()

someRouter.get("/path", null, async req => {
  return {
    result: "success"
  }
})

Routes with JOI schemas

Same, but with JOI schema

import { RouterBuilder } from "@kurai-io/express-router"

const someRouter = new RouterBuilder()

const someSchema = someRouter.generateSchema("params", {
  address: Joi.string().length(42).required()
})

someRouter.get("/path/:address", someSchema, async req => {
  return {
    address: req.params.address
  }
})

Available JOI schema types: params, query, body

License

You can copy and paste the MIT license summary from below.

MIT License

Copyright (c) 2022-2024 Kurai Foundation

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.