English | 简体中文
HOOH pronounce /huː/ It is a back-end framework for building api or MVC services easily. Integrate KOA2 + Typescript + ioredis + typeorm out of the box, free from complicated configuration.
- HOOH
# Installation
npm init hooh <your-project-name>
npm install
# Launch
npm run dev
# See this is successful startup, open a browser to access this address
Application started,http://localhost:8080
.
├── app # The directory where the js file generated after the build is executed
├── nodemon.json
├── package-lock.json
├── package.json
├── logs # Folder for storing logs
├── src # Work list
│ ├── app.ts # Entry file
│ ├── config # Configuration folder
│ │ └── index.ts
│ ├── controller # Controller folder
│ │ ├── home.ts
│ │ └── ormSample.ts
│ ├── entity # The folder where the typeorm entity is stored
│ │ └── user.ts
│ ├── routes # Routing configuration folder
│ │ └── index.ts
│ ├── scheduler # Cron task folder
│ └── view # Template folder
│ └── home
│ └── index.html
├── pm2.json
└── tsconfig.json
The entry file is src/app.ts
// src/app.ts
import hooh from 'hooh'
const options = {
// ...setting item
}
hooh
.createApp(options) // Create an application instance
.start() // start the service
Setting item | Description | Type | Default | Required |
---|---|---|---|---|
APP_PATH | App folder path | string | Defaults to the src folder for development, and to the app folder for production |
NO |
APP_CONTROLLER_PATH | Controller folder path | string | `${APP_PATH}/controller/` | NO |
APP_SCHEDULE_PATH | Cron task folder path | string | `${APP_PATH}/scheduler/` | NO |
routes | Routing configuration | RouteConfig | NO | |
middlewares | For a list of middleware, please refer to the middleware writing in Koa documentation | Koa.Middleware[] | NO |
The configuration file defaults to src/config/index.ts
export default {
configName: 'configValue'
}
Start the scheduled task execution in the entry file
Define route file: Defined in src/routes/index.ts by default
import { RouteConfigItem } from 'hooh'
const routes:RouteConfigItem[] = [
{
match: '/',
controller: 'home.index',
method: 'get',
middlewares: []
},
{
match: '/api/v1/user',
controller: 'api.v1.user.index',
method: 'get'
},
]
export default routes
// src/app.ts
import hooh from 'hooh'
import routes from './routes'
hooh.createApp({
APP_PATH: __dirname,
routes, // The defined route is injected in the createApp method
}).start()
Custom routes are a list, each route contains four properties:
Attributes | Description | Type | Default | Required |
---|---|---|---|---|
match | Routing rules, that is, access paths | string | NO | |
method | Request types supported by this routing rule, such as get , post , put |
string | 'get' | No |
controller | The controller method corresponding to the route. The controller file is placed in the src/controller folder by default. To correspond to the index method in the controller whose file name is home, write it as 'home.index' . If the controller file is in a multi-layer folder, such as the path is src/controller/api/v1/user.ts , call the index method, write it as 'api.v1.user.index' |
string | Yes | |
middlewares | A list of middleware executed before this route executes the controller | Koa.Middleware[] | NO |
Automatic routing is to automatically match the corresponding controller according to the access address without writing a custom routing file. The default is off, and it can be turned on by configuring useAutoRouter: true
// src/config/index.ts
export default {
...
useAutoRouter: true
}
Controller files are placed in the src/controller
folder by default, for example:
// src/controller/home.ts
import { Controller } from 'hooh'
class Home extends Controller { // Inherit the base Controller class
async index() {
this.ctx.body = 'welcome'
}
}
export default Home
ctx.input(Key, Method, Options)
parameter:
- Key: {string} The parameter name of the request. String is returned by default. When not specified, all parameters are taken and returned as a dictionary object.
- Method: {string | null} Request method, one of 'get' | 'post' | 'body' | null. post is another name for body. It is empty by default. If it is not specified (that is, when it is empty), it will be merged after all fetches, and the body will take precedence.
- Options:
- fn: {(any)=>any} Process the acquired parameter and return the processed value
- toNumber: {boolean} Whether to convert to a numeric type, the default is false
- multi: {boolean} Whether the parameter has multiple values, the default is false, when it is true, it is returned as an array
import { Controller } from 'hooh'
class Home extends Controller {
async index() {
// get uid and convert to number
const uid = ctx.input('uid', 'get', {toNumber: false})
// Get the page number, 1 when empty or 0
const page = ctx.input('page', 'get', {fn: (val)=>Number(val) || 1})
}
}
export default Home
ctx.apiReturn(Code: number, Msg: string, Data?: any, Extra?: any)
Parameter
- Code: {number} Interface error code.
- Msg: {string | object} When it is a string, it is the message to be returned, and when it is an object, it is the third parameter, any data of the business.
- Data: {any} Any business data to return, default is empty
- Extra {any} Other extended data to be returned, default is empty
import { Controller } from 'hooh'
class Home extends Controller {
async index() {
this.ctx.apiReturn(0, 'Your message', {rows: [], page: 1})
}
}
export default Home
The above example interface returns the following json string:
{
"code": 0
"msg": "Your message",
"data": {"rows": [], "page": 1},
"date": 1648128308, // The time when the current interface returned, a timestamp in seconds
"extra": null
}
This framework uses Typeorm as the database manipulation tool by default. Please refer to the Typeorm documentation for specific usage. The following is a simple usage example:
Open the ormconfig
configuration item in the src/config/index.ts
configuration file:
// src/config/index.ts
export default {
ormconfig: [
{
name: 'default', // connection name
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: '12345678',
database: 'hoohsite',
entitiesPath: ['entity/site/'], // entity folder path
}, {
... // Support for multiple connection configurations
}
]
...
}
Define an entity named user inside the src/entity/site/
folder
// src/entity/site/user.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
@Entity({name: 'user'})
export class User {
@PrimaryGeneratedColumn()
id: number
@Column('varchar')
account: string
@Column('varchar')
password: string
@Column('varchar')
create_time: string
@Column('tinyint')
status: number
}
// src/controller/ormSample
import { Controller } from 'hooh'
import { User } from '../entity/site/user'
class OrmSample extends Controller {
async index(): Promise<any> {
const connName = 'default'
const userRepository = this.orm.getRepository(User, connName)
// Orm general usage
const rows = await userRepository.findByIds([1, 2, 3])
// Use queryBuilder
const rows2 = await userRepository.createQueryBuilder('user').select().where('user.id < 100').getMany()
// Direct sql statement query
const sql = 'select * from user where id < 100'
const rows3 = await this.orm.getManager(connName).query(sql)
// output
this.ctx.apiReturn(0, {rows, rows2, rows3})
}
}
export default OrmSample
The scheduled task function is based on node-schedule. The following is a simple usage example integrated in this framework:
To use timed tasks, you need to understand cron expressions first:
# cron expressions
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ |
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59)
Define a scheduled task in the src/config/schedules.ts
file
// src/config/schedules.ts
import { ScheduleConfigItem } from 'hooh'
const schedules:ScheduleConfigItem[] = [
{
enable: false, // Whether to enable
cron: '*/10 * * * * *', // Cron expression
handle: 'testCron.index', // The corresponding controller
immediate: false // Whether to execute immediately
}, {
// ...Other scheduled tasks
}
]
export default schedules
Import the configuration just defined in src/config/index.ts
// src/config/index.ts
import schedules from './schedules'
export default {
schedules
// ... other configs
}
Such controllers are placed in the src/scheduler
folder by default. Just configured a timed task whose handle
property is testCron.index
, which means to execute the index method of the testCron controller. In the src/scheduler
folder, define a file testCron.ts, the file is written in the same way as the general controller:
// src/scheduler/testCron.ts
import hooh, { Controller } from 'hooh'
class TestCron extends Controller {
async index(): Promise<void> {
console.log('Example of cron task!')
}
}
export default TestCron
// src/app.ts
import hooh from 'hooh'
const options = {
// ...setting items
}
hooh
.createApp(options)
.scheduleStart() // Add this line to start the scheduled task
.start()
于 src/config/index.ts
Add redis related configuration
// src/config/index.ts
export default {
redis: {
port: 6379, // port
host: '127.0.0.1', // host
password: process.env.APP_REDIS_PASSWORD, // password
family: 4, // 4 (IPv4) or 6 (IPv6)
db: 0
}
}
The redis instance will be hung on the Controller and hooh objects as attributes. For the method, please refer to the ioredis document. The following is an example used in the controller:
import hooh, { Controller } from 'hooh'
class RedisSample extends Controller {
async index(): Promise<any> {
const redis = this.redis
/*
You can also use hooh.redis
const redis = hooh.redis
*/
await redis.set('hooh:testGet', 'test get data')
const getRes = await redis.get('hooh:testGet')
this.ctx.apiReturn(0, {getRes})
}
}
export default RedisSample
import hooh from 'hooh'
async function cacheSample() {
const key = 'my:reids:key'
const data = {
name: 'Cacha Sample'
descript: 'You can set any data'
}
const ex = 60 // Set 60 expiration time
// set cache data sets the cache (when no parameters are passed in, the cache does not expire)
await hooh.redis.cache(key, data, ex)
// get cache data
const cacheData = await hooh.redis.cache(key)
}
import hooh, { Controller } from 'hooh'
class LockSample extends Controller {
async index(): Promise<any> {
const lockKey = 'my:redis:lockKey'
const lockOption = {
ex: 10, // Lock expiration time (seconds), default 10 seconds
loop: 50, // The number of times the lock is requested in a loop, the default is 50 times
wait: 20, // When the lock fails, the time to wait for the next unlock (milliseconds), the default is 20 milliseconds
}
// lock
await hooh.redis.lock(lockKey, lockOption)
/*
... Other business codes
*/
// Remember to unlock after executing the code
await hooh.redis.unlock(lockKey)
}
}
export default LockSample
@lock(Key, LockOptions)
@lock(Key, LockOptions)
Contains key and ex parameters
Key: string The key to store data for redis, the corresponding request parameters can be replaced by '${paramName}' in the string
LockOptions: Configuration items include:
- ex: {number} Lock expiration time (seconds), default 10 seconds
- loop: {number} The number of times the lock is requested in a loop, the default is 50 times
- wait: {number} When the lock fails, the time to wait for the next unlock (milliseconds) The default is 20 milliseconds
- lockReturn: {string} The default value is json'. When it is 'json', whether to return the lock as json when the lock fails (locked by others), usually returns
{"code": -1, "msg": "Locked"}
. Other values will result in an error (return status code 500).- errorCode: {number} When returned in json, the value of code, the default is -1, the default value can be modified by the
lockedErrorCode
of the configuration file
import hooh, { Controller } from 'hooh'
import { lock } from 'hooh/dist/decorators'
class LockSample extends Controller {
@lock('my:redis:lockKey:${uid}', { ex: 10, loop: 5 })
async index(): Promise<any> {
// The incoming uid parameter will automatically replace ${uid} in 'my:redis:lockKey:${uid}'.
// Supports automatic substitution of multiple parameters.
const uid = this.ctx.input('uid')
await hooh.redis.lock(lockKey, lockOption)
/*
... Other business codes
*/
// After the code is executed, it will be automatically unlocked
}
}
export default LockSample
Add cache to api controller.
@apiCache(key, ex)
Containskey
andex
parameters
- Key: {string} To cache the key, you can use '${paramName}' to replace the corresponding request parameter in the string
- ex: {number} Indicates the cache expiration time, in seconds, the default is 300 seconds
import hooh, { Controller } from 'hooh'
import { apiCache } from 'hooh/dist/decorators'
class ApiCacheSample extends Controller {
@apiCache('my:redis:apiCacheKey:${uid}', 60 * 5 )
async index(): Promise<any> {
// The incoming uid parameter will automatically replace ${uid} in 'my:redis:apiCacheKey:${uid}'.
// Supports automatic substitution of multiple parameters.
const uid = this.ctx.input('uid')
/*
... Other business codes
*/
this.ctx.apiReturn(0, {
// any data
})
}
}
export default LockSample
Log based on log4js
import hooh from 'hooh'
// Logs can be added at the following different log levels
hooh.logger.debug("Your logger message.")
hooh.logger.info("Your logger message.")
hooh.logger.warn("Your logger message.")
hooh.logger.error("Your logger message!")
hooh.logger.fatal("Your logger message.")
The log will be recorded in the log/
folder, and the terminal will also output:
[2022-03-24 22:14:37.987] [info] default -Yor logger message!
Execute npm run build
, the installer will generate the translated js file in the app/
directory.
app/app.js
is the startup file of the project.
npm run build
To use PM2
, you need to install it globally. If pm2 is not installed, execute:
npm install -g pm2
When the project is created, a pm2.json file will be generated in the project root directory. If not, please write your own pm2.json
// pm2.json
{
"apps": [{
"name": "Your-project-name",
"script": "app/app.js",
"cwd": "/Your/project/upload/path",
"exec_mode": "fork",
"max_memory_restart": "1G",
"autorestart": true,
"node_args": [],
"args": [],
"env": {
"NODE_ENV": "production"
}
}]
}
pm2 start pm2.json