1.5.6 • Published 6 months ago

@cheermix/rest v1.5.6

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

A perfectly mixed cocktail

code style gitter npm version npm download npm license

CheerMix is an integrated framework based on Express and other tool libraries. CheerMix selects tool libraries that are lighter, more robust, easier to learn and less demanding, allowing developers to focus more on their services. Code is designed to help developers build servers more quickly. CheerMix integrates these tools very well. It is simply an art, making your code written more declarative and functional. You can even view the code as a document, programming and writing documents are the same.

Getting Started

It is recommended that you use the latest node version. CheerMix has not tested the compatibility with lower versions.

Installation

npm i -S @cheermix/rest

Hello World

First, you create a main.ts in the root directory, and then create a folder called routes. You can name it whatever you want. I like to put the x.route.ts and x.spec.ts files here. I also create another folder called services. I like to separate reading and writing, such as x.command.ts and x.query.ts.

// file: routes/hello.route.ts
import joi from "joi"
import {
  ExpressRouter,
  asyncMiddleware,
  validQuery,
  validResponseDataAndSendIt,
} from "@cheermix/rest"

const helloworld: ExpressRouter = {
  method: "GET",
  path: "/helloworld",
  middlewares: [
    validQuery({
      // CheerMix integrates joi so you don't need to install it
      mustNumber: joi.number().integer(),
      name: joi.string().default("cheermix"),
    }),
    asyncMiddleware(async function (reqeust, response) {
      response.data = {
        message: `${request.data.query.name}: hello world!`,
      }
    }),
    validResponseDataAndSendIt({
      message: joi.string().required(),
    }),
  ],
}

export default [
  helloworld,
  // Another ExpressRouters
]

// file: main.ts
import "dotenv/config"
import { startup } from "@cheermix/rest"
import { flatten } from "lodash"
import helloRoutes from "./routes/hello.route"

// I'm going to put these routes into another array,
// so routers is a two-dimensional array,
// so we're going to use lodash's flatten to flatten it.
const routers = flatten([
  helloRoutes,
  // Another Routes
])

export default startup(routers)

Testing

I like to use mocha and supertest for testing, let’s add it to the project.

npm i -D mocha supertest @types/mocha @types/supertest ts-mocha ts-node typescript

Let's create a hello.spec.ts in the routes folder.

// file: routes/hello.spec.ts
import app from "../main"
import joi from "joi"
import request from "supertest"

describe("Hello", function () {
  it("Test /helloworld", async function () {
    await request(app).get("/helloworld?mustNumber=Satoshi").expect(400)
    await request(app)
      .get("/helloworld?name=Satoshi")
      .expect(200)
      .then(function (response) {
        joi.assert(response.body.message, joi.string().required())
      })
  })
})

Jwt Authentication

Now we need to set login restrictions for /helloworld. You need to place the token in the Authorization header of the request, such as: Authorization: Bearer \.

// file: routes/hello.route.ts
import {
  ExpressRouter,
  asyncMiddleware,
  validQuery,
  validResponseDataAndSendIt,
  hasAuthorization, // 1.import this middleware
} from "@cheermix/rest"

const helloworld: ExpressRouter = {
  method: "GET",
  path: "/helloworld",
  middlewares: [
    hasAuthorization, // 2.using it
    validQuery({
      mustNumber: joi.number().integer(),
      name: joi.string().default("cheermix"),
    }),
    asyncMiddleware(async function (reqeust, response) {
      // 5.Get the user info
      request.user?.name
      response.data = {
        message: `${request.data.query.name}: hello world!`,
      }
    }),
    validResponseDataAndSendIt({
      message: joi.string().required(),
    }),
  ],
}

// file: main.ts
// 4.You can override the user type in ExpressRequest
//   so that you have corresponding field prompts in the IDE
declare global {
  namespace Express {
    interface Request {
      user?: {
        userId: string
        name: string
        roles: string[]
      }
    }
  }
}

export default startup(routers, {
  // 3.Implement the logic of findUser
  async authenticationFindUserLogic(payload) {
    const userId = payload.sub as string
    // Fetch DB or write it in services/user.query.ts ...
    return {
      userId,
      name: "Satoshi",
      roles: ["admin"],
    }
  },
})

Well done, you've learned.

Exception Handling

CheerMix has common exception types and unified exception handling, which look like this:

export abstract class HttpServerException extends Error {
  abstract getHttpResponseStatusCode(): number
}

export class WrongParameterException extends HttpServerException {
  getHttpResponseStatusCode(): number {
    return 400
  }
}

export class NotFoundException extends HttpServerException {
  getHttpResponseStatusCode(): number {
    return 404
  }
}

export class UnauthorizedException extends HttpServerException {
  getHttpResponseStatusCode(): number {
    return 401
  }
}

export class NoPermissionException extends HttpServerException {
  getHttpResponseStatusCode(): number {
    return 403
  }
}

export class ServerErrorException extends HttpServerException {
  getHttpResponseStatusCode(): number {
    return 500
  }
}

When you throw these exceptions in any RequestHandler wrapped by asyncMiddleware, CheerMix will handle the corresponding response code, and you will also receive exception information in the response body. It can be said that all the Middleware provided by CheerMix has been wrapped.

// file: routes/hello.route.ts
import {
  ExpressRouter,
  asyncMiddleware,
  validQuery,
  validResponseDataAndSendIt,
  NotFoundException, // 1.import exception type
} from "@cheermix/rest"
// import ...
// const helloworld: ExpressRouter ...

// Let's add another ExpressRouter
const findSomething: ExpressRouter = {
  method: "GET",
  path: "/something/:somethingId",
  middlewares: [
    validParam({
      somethingId: joi.number().integer().greater(1).required(),
    }),
    asyncMiddleware(async function (reqeust, response) {
      throw new NotFoundException("something doesn't exist") // 2.using it
    }),
  ],
}

export default [helloworld, findSomething]

If you feel that these Exceptions are not enough, you can expand HttpServerException.

// file: errors.ts
import { HttpServerException } from "@cheermix/rest"

export class TeapotException extends HttpServerException {
  getHttpResponseStatusCode(): number {
    return 418
  }
}

// file: routes/hello.route.ts
import { TeapotException } from "../errors.ts"

const findSomething: ExpressRouter = {
  method: "GET",
  path: "/something/:somethingId",
  middlewares: [
    validParam({
      somethingId: joi.number().integer().greater(1).required(),
    }),
    asyncMiddleware(async function (reqeust, response) {
      throw new TeapotException(
        "Haha, want to crawl our data again? It's time for me to catch you."
      )
    }),
  ],
}

// Omit some codes ...

Environment

CheerMix has some environment variables that can be used to make adjustments to the integrated tools. CheerMix is easy to configure. I like to use dotenv to scan my .env file and load variables into the node process. You can also put these variables in vscode's launch.json or pm2's ecosystem.config.js. Dotenv is not directly integrated here and is up to you.

NameTypeDefault ValueDescription
LOGGER_LEVEL"error""warn""info""http""debug""debug"Log printing level
LOGGER_COLORIZEbooleantrueLog coloring
LOGGER_TEXT_ALIGNbooleantrueLog text format adaptive
LOGGER_TIME_FORMATstringYYYY-MM-DD HH:mm:ss.SSSLog time format
NODE_TIMEZONEstringAsia/Hong_KongDayjs timezone
NODE_PORTinteger80Server port
NODE_ENVstringdevelopmentRuntime environment
EXPRESS_REQUEST_LIMIT_TIMEFRAMEmillisecond1000Limit the time range of requests for the same IP for a certain period of time
EXPRESS_REQUEST_LIMIT_MAXinteger20Limit the maximum number of requests for the same IP within a certain period of time
JWT_SECRETstringhello world!Jwt Secret
JWT_EXPIRESmillisecond48 hoursJwt expiration time
JWT_REFRESH_TIMEmillisecond1 hourJwt refresh time

API Reference

interface ExpressRouter

Convenient for you to write the route of type

FieldTypeDescription
method"GET","POST","PUT","DELETE"Http method
pathstringUri
middlewaresMiddlewareExpress Middleware

interface StartupOptions

Startup Options

FieldTypeDescription
authenticationFindUserLogic?(payload: JwtPayload) => Promise\Specify your findUser implementation
middlewaresExtension?Middleware[]You can also expand other Middleware

const hasAuthorization: Middleware

Verify whether your request header contains hasAuthorization

const file: multer.Multer

File upload and download tool, see Multer for specific usage.

function asyncMiddleware(asyncHandler): Middleware

A wrapper for the async RequestHandler which will then catch the Exception

ParameterTypeDescription
asyncHandler(reqeust: Request, response: Response) => Promise\Your RequestHandler

function validParam(joiSchema): Middleware

Verify the variables on the path and automatically convert the type and set the default value. The data can be found in request.data.param

ParameterTypeDescription
joiSchemaRecord<string, joi.AnySchema>Joi schema

function validQuery(joiSchema): Middleware

Verify the parameters after "?" in the path and automatically convert the type and set the default value. You can find the data in request.data.query

ParameterTypeDescription
joiSchemaRecord<string, joi.AnySchema>Joi schema

function validBody(joiSchema): Middleware

Verify the json in the request body and automatically convert the type and set the default value. The data can be found in request.data.body

ParameterTypeDescription
joiSchemaRecord<string, joi.AnySchema>Joi schema

function validResponseDataAndSendIt(joiSchema): Middleware

Verify the data in response.data and respond

ParameterTypeDescription
joiSchemaRecord<string, joi.AnySchema>Joi schema

function signPassword(password): string

Bcrypt encryption

ParameterTypeDescription
passwordstring

function validPassword(password, saltyPassword): boolean

Verify password

ParameterTypeDescription
passwordstringPassword before encryption
saltyPasswordstringEncrypt the salted ciphertext

function signJwt(subject, refreshToken?): { accessToken: string; refreshToken: string }

Sign Json web token

ParameterTypeDescription
subjectstringUsually put the user ID
refreshToken?stringBefore the JWT_EXPIRES setting expires, you can get a new access token by passing in the Refresh token.
1.5.6

6 months ago

1.5.5

7 months ago

1.5.4

7 months ago

1.5.3

7 months ago

1.5.2

7 months ago

1.5.1

7 months ago

1.5.0

7 months ago

1.4.0

7 months ago

1.3.0

7 months ago

1.2.1

7 months ago

1.2.0

7 months ago

1.1.11

7 months ago

1.1.10

7 months ago

1.1.9

7 months ago

1.1.8

7 months ago

1.1.7

7 months ago

1.1.6

7 months ago

1.1.5

7 months ago

1.1.4

7 months ago

1.1.3

7 months ago

1.1.2

7 months ago

1.1.1

7 months ago

1.1.0

7 months ago

1.0.5

7 months ago

1.0.4

7 months ago

1.0.3

7 months ago

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago