A build system and framework for Node like Next.js
Bus is a one-stop solution for Node.js. Inspired by Vue-cil, Next.js, and Backpack. Bus lets you set up a koa server by just running one command. You can use Babel, Webpack, Swagger, Mongoose in your project with less config.
Install it:
npm install -g bus-core
bus init
// or
npx bus-core init
Init project:
cd yourproject
npm install
and edit the config file:src/config/index.js like this:
{
port: 3000,
mongodb: {
url: 'mongodb://test:test@mongoBaseUrl/test'
}
}
npm run dev and go to http://localhost:3000/api/swagger-html
before start your project, check the options in the src/index.js
import Bus from "bus-core";
import Api from "./apis";
import Model from "./models";
import Schema from "./schemas";
const bus = new Bus({
config: {
serect: 'bus',
excludeCheckUrl: [
{url: /user$/, methods: ['GET']}
]
},
Api,
Model,
Schema
})
Param | Type | Description |
---|---|---|
[mongodb] | Object |
mongodb options |
[mongodb.url] | String |
mongodb url |
[mongodb.options] | Object |
mongoose connent options |
[logsPath] | String |
logs output paths(default: ./logs) |
[apiPrefix] | String |
prefix add before every route |
[swaggerConfig] | Object |
swagger options |
[port] | String |
server port |
[jwt] | Object |
jwt options |
[jwt.secret] | String |
jwt secret(use to create a token) |
[jwt.excludeUrls] | Array |
unauth routes |
Example:
new Bus({
config: {
port: 3000,
apiPrefix: 'api',
mongodb: {
url: 'mongodb://test:test@localhost:27017/test',
options: {
useNewUrlParser: true,
poolSize: 10
}
},
swaggerConfig: {
title: 'Swagger Test'
},
jwt: {
secret: 'bus',
excludeUrls: [
/\/test/,
{url: '/user', methods: ['GET']}
]
}
}
})
if you have set jwt option in your config, you can import the bus instance to create a token Example:
import bus from '../index.js'
class User {
@request('POST', '/user/login')
@summary('login api')
@tag
@body({
username: {
type: 'string',
description: 'username'
},
password: {
type: 'string',
description: 'password'
},
})
static async login(ctx) {
const {username, password} = ctx.request.body
const user = await userModel.findOne({username, password})
if(user) {
let data = {fullName: user.fullName}
let token = bus.Token.create(data, '30d')
ctx.body = {
user: data,
token
}
} else {
throw new ApiError(ApiErrorNames.RESOURCES_EXIST)
}
}
}
The schema is used for either swagger's responses and mongoose's schema
defind a schmea
// user.js
const mongoose = require('mongoose')
module.exports = {
username: {
type: String,
example: 'admin', // use for swagger
unique: true,
required: true
},
role_id: {
type: mongoose.Types.ObjectId,
typeSwagger: String, // use for swagger
example: 'role',
ref: 'Role'
},
}
Model for mongoose(check here)
tips: each model will be added three default methods(check here)
// user.js
module.exports = (schema, models) => {
schema
.virtual('fullName').get(function () {
return this.name.first + ' ' + this.name.last;
});
}
To combine swagger with api, we use using decorator to make api definition(koa-swagger-decorator)
beneath params will be given to your module
Param | Type | Description |
---|---|---|
[examples] | Object |
swagger examples can be used in body or responses |
[models] | Object |
mongoose model |
[tag] | Object |
swagger decorator tag |
[decorator] | Object |
swagger decorator |
[ApiError] | Object |
error object |
[ApiErrorNames] | String |
error names |
return example
Param | Type | require | Description |
---|---|---|---|
[commonApiConfig] | Object |
false | config for commonapi e.g. {baseUrl, name} |
[ApiClass] | Object |
true | your api class |
module.exports = ({
examples,
models,
tag,
decorator: {
request,
summary,
body,
middlewares,
path,
description,
query,
responses
},
ApiError,
ApiErrorNames
}) => {
const userModel = models.user
const userExample = examples.user
class User {
@request('GET', '/users')
@summary('获取用户信息')
@tag
@query({
filter: {
type: 'string',
description: 'username filter'
},
})
@responses(userExample)
// cover common api
static async query(ctx) {
const {filter} = ctx.query
return userModel.like(filter, 'username') // this function is inherit from bus helper
.then(user => ctx.body = user)
.catch(err => {
throw new ApiError(null, 400, err.message)
})
}
}
return {
commonApiConfig: { // inherit common api
name: 'user',
baseUrl: 'users',
},
ApiClass: User
}
}
if you want to extend your apiClass with commonApi, here's what you need to know
-
cover the common api you can cover the common api by define the same function name, such as get, post, query, put, delete, deleteBatch
-
change the url {prefix}/{filename} is the default api url, if you set the baseUrl, it will be change to this {prefix}/{baseUrl}/{filename}
-- The Document order is the same as the execution order --
- onInitMongoose
// will be call after mongoose.connect onInitMongoose(mongoose)
- onInitSchema
// will be call when fromat scheam and example, you can add unify attributes for both of them here await hooks.onInitSchema(name, data) // promise // return fromatedData
- onInitModels
// will be call before use mongoose.model(name, Schema) await hooks.onInitModels(name, Schema) // promise // return fromatedSchema
- onInitApi
// will be call before add to router, you can add unify methods for each apiClass. // args is the same as the one pass to apiClass await hooks.onInitApi(name, apiClass, args) // promise
- onInitMiddlewares
// will be call when init middlewares, you can add your middlewares by modifty the middlewares argument. this.hooks.onInitMiddlewares(middlewares, app) // e.g. // onInitMiddlewares(middlewares) { // middlewares.unshift(koaStatic(path.join( __dirname, '../client'), {gzip: true})) // }
- onTokenCheck
// will be call before request pass to api(exclude jwt.excludeUrls) await this.hooks.onTokenCheck(decode, ctx) // promise, the decode jwt data will be the first argument
- beforeApiEnter
// will be call before request pass to api(for all apis) await beforeApiEnter(ctx, next) // promise
- beforeApiResolve
// will be call before request resloved(for all apis), you can format the result here ctx.body = (beforeApiResolve && await beforeApiResolve(ctx)) || { // promise success: true, data: ctx.body }
enumeration
- ApiErrorNames.PARAMS_ERROR, status:400, message: 'Request parameter is wrong'
- ApiErrorNames.RESOURCES_EXIST, status:400, message: 'Data already exists'
- ApiErrorNames.USER_NOT_PERMISSIONS, status:401, message: 'User has no permissions'
- ApiErrorNames.RESOURCES_NOT_EXIST, status:404, message: 'There is no resources'
- ApiErrorNames.SERVER_ERROR, status:500, message: 'Internal Server Error'
- ApiErrorNames.INVALID_CODE, status:400, message: 'Invalid code'
e.g.
new ApiError(ApiErrorNames.PARAMS_ERROR)
For custom advanced behavior, you can create a bus.config.js in the root of your project's directory (next to package.json).
To extend webpack, you can define a function that extends its config in bus.config.js.
// bus.config.js
module.exports = {
webpack(config) {
let loaders = config.module.rules
loaders.push( {
test: /\.html$/,
loader: 'raw-loader'
})
return config
}
}
To extend our usage of babel, you can define a .babelrc file at the root of your app. This file is optional.
If found, Bus will consider it to be the source of truth. Thus it must define what Bus needs as well, which is the bus-core/babel preset.
This is designed so that you are not surprised by modifications we could make to the default babel configurations.
Here's an example .babelrc file:
{
"presets" : ["bus-core/babel"],
"env": {
"production": {
"plugins": [
["transform-remove-console", { "exclude": [ "error", "warn", "info"] }]
]
}
}
}
Generate a template (only support base template now ...)
Runs bus in development mode.
Your code will reload if you make edits.
You will see the build errors in the console that look like this.
Builds the app for production to the build
folder.
It correctly bundles your production mode and optimizes the build for the best performance.
You can run your production application with the following command:
node ./build/main.js
Your application is ready to be deployed!
Note: Make sure to add the build
directory to your .gitignore
to keep compiled code out of your git repository
- jaredpalmer/backpack(if you only need a minimalistic it's a better choice.)
- zeit/next.js
- vuejs/vue-cli
- Yohann ([email protected])