2021-10-28 14:55:51 +00:00
---
order: 1
---
# Server-side Kernel
2021-10-31 01:44:52 +00:00
## Microservices
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
To understand NocoBase faster, let's create an application with a new app.js file with the following code.
2021-10-28 14:55:51 +00:00
```ts
const { Application } = require('@nocobase/server');
const app = new Application({
2021-10-31 01:44:52 +00:00
// omit the configuration information
2021-10-28 14:55:51 +00:00
});
2021-10-31 01:44:52 +00:00
// configure a users table
2021-10-28 14:55:51 +00:00
app.collection({
name: 'users',
fields: [
{ type: 'string', name: 'username' },
{ type: 'password', name: 'password' }
],
});
2021-10-31 01:44:52 +00:00
// parse argv arguments, terminal does different operations via command line
2021-10-28 14:55:51 +00:00
app.parse(process.argv);
```
2021-10-31 01:44:52 +00:00
The terminal runs
2021-10-28 14:55:51 +00:00
```bash
2021-10-31 01:44:52 +00:00
# Generate database table structure based on configuration
2021-10-28 14:55:51 +00:00
node app.js db:sync
2021-10-31 01:44:52 +00:00
# Start the application
2021-10-28 14:55:51 +00:00
node app.js start --port=3000
```
2021-10-31 01:44:52 +00:00
The REST API for the relevant users table is generated
2021-10-28 14:55:51 +00:00
```bash
2021-10-31 01:44:52 +00:00
GET http://localhost:3000/api/users
POST http://localhost:3000/api/users
GET http://localhost:3000/api/users/1
PUT http://localhost:3000/api/users/1
DELETE http://localhost:3000/api/users/1
2021-10-28 14:55:51 +00:00
```
2021-10-31 01:44:52 +00:00
The above example creates a real working REST API service in just about 10 lines of code. In addition to the built-in REST API, you can also customize other actions such as login, registration, logout, etc. via ``app.actions()`.
2021-10-28 14:55:51 +00:00
```ts
app.actions({
async login(ctx, next) {},
async register(ctx, next) {},
async logout(ctx, next) {},
}, {
2021-10-31 01:44:52 +00:00
resourceName: 'users', // resource belonging to users
2021-10-28 14:55:51 +00:00
});
```
2021-10-31 01:44:52 +00:00
The HTTP API for the above custom operation is
2021-10-28 14:55:51 +00:00
```bash
2021-10-31 01:44:52 +00:00
POST http://localhost:3000/api/users:login
POST http://localhost:3000/api/users:register
POST http://localhost:3000/api/users:logout
2021-10-28 14:55:51 +00:00
```
2021-10-31 01:44:52 +00:00
The custom HTTP API remains in the style of the REST API, represented in the ``< resourceName > :< actionName > ` format. In fact, the REST API can also explicitly specify ``actionName``, and when ``actionName`` is specified, it does not matter what request method is used, e.g.
2021-10-28 14:55:51 +00:00
```bash
2021-10-31 01:44:52 +00:00
# Update actions
PUT http://localhost:3000/api/users/1
# is equivalent to
POST http://localhost:3000/api/users:update/1
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
# Delete operation
DELETE http://localhost:3000/api/users/1
# Equivalent to
GET http://localhost:3000/api/users:destroy/1
# Equivalent to
POST http://localhost:3000/api/users:destroy/1
2021-10-28 14:55:51 +00:00
```
2021-10-31 01:44:52 +00:00
NocoBase's Resourcer is designed based on Resource and Action, combining REST and RPC to provide a more flexible and unified Resource Action API. combined with the client SDK it looks like
2021-10-28 14:55:51 +00:00
```ts
const { ClientSDK } = require('@nocobase/sdk');
const api = new ClientSDK({
2021-10-31 01:44:52 +00:00
// can be adapted to different requests
2021-10-28 14:55:51 +00:00
request(params) => Promise.resolve({}),
});
await api.resource('users').list();
await api.resource('users').create();
await api.resource('users').get();
await api.resource('users').update();
await api.resource('users').destroy();
await api.resource('users').login();
await api.resource('users').register();
await api.resource('users').logout();
```
2021-10-31 01:44:52 +00:00
## Application
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
NocoBase's Application inherits Koa, integrates with DB and CLI, adds some essential APIs, here are some highlights.
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
- `app.db` : database instance, each app has its own db.
- `db.getCollection()` data table/dataset
- `collection.repository` data warehouse
- `collection.model` data model
- `db.on()` Add event listener, provided by EventEmitter
- ` db.exit()` Triggers an event, provided by EventEmitter
- `db.exitAsync()` Triggers an asynchronous event
- `app.cli` , the Commander instance, provides command-line operations
- `app.context` , the context
2021-10-28 14:55:51 +00:00
- `ctx.db`
2021-10-31 01:44:52 +00:00
- `ctx.action` , the current resource operation instance
- `action.params` operation parameters
- `action.mergeParams()` parameter merge method
- `app.constructor()` initialization
- `app.collection()` Define data Schema, equivalent to `app.db.collection()`
- `app.resource()` Define resources
- `app.actions()` defines the resource's action methods
- `app.on()` Add event listeners, provided by EventEmitter
- `app.exit()` Triggers an event, provided by the EventEmitter
- `app.exitAsync()` Triggers an asynchronous event
- `app.use()` Add middleware, provided by Koa
- `app.command()` Custom command line, equivalent to `app.cli.command()`
- `app.plugin()` Add plugins
- `app.load()` Load configuration, mainly for loading plugins
- `app.parse()` parse argv arguments, written at the end, equivalent to `app.cli.parseAsync()`
## Collection
NocoBase defines the Schema of the data through the `app.collection()` method, the types of Schema include
Attribute
- Boolean Boolean
- String String
- Text long text
- Integer integer
- Float Floating-point
- Decimal Currency
- Json/Jsonb/Array Different database JSON types are not the same, there are compatibility problems
- Time Time
- Date
- Virtual Virtual fields
- Reference
- Formula Calculation formula
- Context Context
- Password Password
- Sort Sort
Relationships Association/Realtion
- HasOne One-to-One
- HasMany One-to-Many
- BelongsToMany
- BelongsToMany Many-to-many
- Polymorphic Polymorphism
For example, the table structure of a micro-blog can be designed like this
2021-10-28 14:55:51 +00:00
```ts
2021-10-31 01:44:52 +00:00
// users
2021-10-28 14:55:51 +00:00
app.collection({
name: 'users',
fields: {
username: { type: 'string', unique: true },
password: { type: 'password', unique: true },
2021-10-31 01:44:52 +00:00
posts: { type: 'hasMany' },
2021-10-28 14:55:51 +00:00
},
});
2021-10-31 01:44:52 +00:00
// Articles
2021-10-28 14:55:51 +00:00
app.collection({
name: 'posts',
fields: {
2021-10-31 01:44:52 +00:00
title: 'string',
content: 'text',
tags: 'believesToMany',
2021-10-28 14:55:51 +00:00
comments: 'hasMany',
2021-10-31 01:44:52 +00:00
author: { type: 'belongsTo', target: 'users' }
2021-10-28 14:55:51 +00:00
},
});
2021-10-31 01:44:52 +00:00
// Tags
2021-10-28 14:55:51 +00:00
app.collection({
name: 'tags',
fields: [
2021-10-31 01:44:52 +00:00
{ type: 'string', name: 'name' }
2021-10-28 14:55:51 +00:00
{ type: 'belongsToMany', name: 'posts' },
],
});
2021-10-31 01:44:52 +00:00
// Comments
2021-10-28 14:55:51 +00:00
app.collection({
name: 'comments',
fields: [
{ type: 'text', name: 'content' },
{ type: 'belongsTo', name: 'user' },
],
});
```
2021-10-31 01:44:52 +00:00
In addition to configuring the schema via `app.collection()` , you can also directly call the api to insert or modify the schema, the core API of collection are
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
- `collection` The data structure of the current collection
- `collection.hasField()` Determine if a field exists
- `collection.addField()` Add a field configuration
- `collection.getField()` Get the field configuration
- `collection.removeField()` Remove a field configuration
- `collection.sync()` Synchronize with database table structure
- `collection.repository` The data repository for the current collection
2021-10-28 14:55:51 +00:00
- `repository.findMany()`
- `repository.findOne()`
- `repository.create()`
- `repository.update()`
- `repository.destroy()`
- `repository.relatedQuery().for()`
- `create()`
- `update()`
- `destroy()`
- `findMany()`
- `findOne()`
- `set()`
- `add()`
- `remove()`
- `toggle()`
2021-10-31 01:44:52 +00:00
- `collection.model` The data model of the current collection
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Collection example.
2021-10-28 14:55:51 +00:00
```ts
const collection = app.db.getCollection('posts');
collection.hasField('title');
collection.getField('title');
2021-10-31 01:44:52 +00:00
// Add or update
2021-10-28 14:55:51 +00:00
collection.addField({
type: 'string',
name: 'content',
});
2021-10-31 01:44:52 +00:00
// Remove
2021-10-28 14:55:51 +00:00
collection.removeField('content');
2021-10-31 01:44:52 +00:00
// add, or specify a key path to replace
2021-10-28 14:55:51 +00:00
collection.mergeField({
name: 'content',
type: 'string',
});
2021-10-31 01:44:52 +00:00
In addition to the global `db.sync()` , there is also the `collection.sync()` method.
2021-10-28 14:55:51 +00:00
await collection.sync();
```
2021-10-31 01:44:52 +00:00
`db:sync` is one of the very commonly used command lines to generate a table structure from the collection's schema. See the CLI section for more details. After ``db:sync``, you can write data to the table, either using Repository or Model operations.
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
- Repository initially provides findAll, findOne, create, update, destroy core operations.
- Model. See the Sequelize documentation for detailed instructions on how to use it.
- Model depends on the adapted ORM, Repository provides a unified interface based on Model.
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Creating data via Repository
2021-10-28 14:55:51 +00:00
```ts
const User = app.db.getCollection('users');
const user = await User.repository.create({
title: 't1',
content: 'c1',
author: 1,
tags: [1,2,3],
}, {
whitelist: [],
blacklist: [],
});
await User.repository.findMany({
filter: {
title: 't1',
},
fields: ['id', 'title', 'content'],
sort: '-created_at',
page: 1,
perPage: 20,
});
await User.repository.findOne({
filter: {
title: 't1',
},
fields: ['id', 'title', 'content'],
sort: '-created_at',
page: 1,
perPage: 20,
});
await User.repository.update({
title: 't1',
content: 'c1',
author: 1,
tags: [1,2,3],
}, {
filter: {},
whitelist: [],
blacklist: [],
});
await User.repository.destroy({
filter: {},
});
```
2021-10-31 01:44:52 +00:00
Create data from Model
2021-10-28 14:55:51 +00:00
```ts
const User = db.getCollection('users');
const user = await User.model.create({
title: 't1',
content: 'c1',
});
```
2021-10-31 01:44:52 +00:00
## Resource & Action
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Resource is an Internet resource, and all Internet resources correspond to an address. In REST, the request method (GET/POST/PUT/DELETE) is used to identify the specific action, but the request method is rather limited, for example, the above mentioned login, registration and logout cannot be represented by REST API. To solve such problems, NocoBase represents resource actions in `<resourceName>:<actionName>` format. In the world of the relational model, relationships are everywhere, and based on relationships, NocoBase extends the concept of relational resources, with actions corresponding to relational resources in the format `<associatedName>. <resourceName>:<actionName>` .
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
The Collection is automatically synchronized to the Resource, as defined in the Schema in the Collection section above, and the resources that can be refined are
2021-10-28 14:55:51 +00:00
- `users`
- `users.posts`
- `posts`
- `posts.tags`
- `posts.comments`
- `posts.author`
- `tags`
- `tags.posts`
- `comments`
- `comments.user`
2021-10-31 01:44:52 +00:00
< Alert title = "Relationship and differences between Collection and Resource" type = "warning" >
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
- Collection defines the schema (structure and relationships) of the data
- Resource defines the action of the data
- The data structure of the Resource request and response is defined by the Collection
- Collection is automatically synchronized to Resource by default
- The concept of Resource is much larger and can interface to external data or other customizations in addition to the Collection
2021-10-28 14:55:51 +00:00
< / Alert >
2021-10-31 01:44:52 +00:00
Resource-related APIs are.
2021-10-28 14:55:51 +00:00
- `app.resource()`
- `app.actions()`
- `ctx.action`
2021-10-31 01:44:52 +00:00
A resource can have multiple actions.
2021-10-28 14:55:51 +00:00
```ts
2021-10-31 01:44:52 +00:00
// Data classes
2021-10-28 14:55:51 +00:00
app.resource({
name: 'users',
actions: {
async list(ctx, next) {},
async get(ctx, next) {},
2021-10-31 01:44:52 +00:00
async create(ctx, next) {}, async create(ctx, next) {},
2021-10-28 14:55:51 +00:00
async update(ctx, next) {},
2021-10-31 01:44:52 +00:00
async destroy(ctx, next) {}, async destroy(ctx, next) {},
2021-10-28 14:55:51 +00:00
},
});
2021-10-31 01:44:52 +00:00
// Non-data classes
2021-10-28 14:55:51 +00:00
app.resource({
name: 'server',
actions: {
2021-10-31 01:44:52 +00:00
// Get the server time
2021-10-28 14:55:51 +00:00
getTime(ctx, next) {},
2021-10-31 01:44:52 +00:00
// Health check
2021-10-28 14:55:51 +00:00
healthCheck(ctx, next) {},
},
});
```
2021-10-31 01:44:52 +00:00
General operations can be used for different resources
2021-10-28 14:55:51 +00:00
```ts
app.actions({
async list(ctx, next) {},
async get(ctx, next) {},
async create(ctx, next) {},
async update(ctx, next) {},
async destroy(ctx, next) {},
}, {
2021-10-31 01:44:52 +00:00
// shared globally if resourceName is not specified
2021-10-28 14:55:51 +00:00
resourceNames: ['posts', 'comments', 'users'],
});
```
2021-10-31 01:44:52 +00:00
The action defined inside the resource will not be shared, regular operations like adding, deleting, changing and checking are recommended to be set as global, `app.resource()` only set parameters, e.g.
2021-10-28 14:55:51 +00:00
```ts
app.resource({
name: 'users',
actions: {
list: {
2021-10-31 01:44:52 +00:00
fields: ['id', 'username'], // output only the id and username fields
2021-10-28 14:55:51 +00:00
filter: {
2021-10-31 01:44:52 +00:00
'username.$ne': 'admin', // data range filtering filter username ! = admin
2021-10-28 14:55:51 +00:00
},
2021-10-31 01:44:52 +00:00
sort: ['-created_at'], // reverse order of creation time
2021-10-28 14:55:51 +00:00
perPage: 50,
},
get: {
2021-10-31 01:44:52 +00:00
fields: ['id', 'username'], // output only the id and username fields
2021-10-28 14:55:51 +00:00
filter: {
2021-10-31 01:44:52 +00:00
'username.$ne': 'admin', // data range filtering filter username ! = admin
2021-10-28 14:55:51 +00:00
},
},
create: {
2021-10-31 01:44:52 +00:00
fields: ['username'], // whitelist
2021-10-28 14:55:51 +00:00
},
update: {
2021-10-31 01:44:52 +00:00
fields: ['username'], // whitelist
2021-10-28 14:55:51 +00:00
},
destroy: {
2021-10-31 01:44:52 +00:00
filter: { // cannot delete admin
2021-10-28 14:55:51 +00:00
'username.$ne': 'admin',
},
},
},
});
2021-10-31 01:44:52 +00:00
// The app has built-in list, get, create, update, destroy operations by default
2021-10-28 14:55:51 +00:00
app.actions({
async list(ctx, next) {},
async get(ctx, next) {},
2021-10-31 01:44:52 +00:00
async create(ctx, next) {}, async create(ctx, next) {},
2021-10-28 14:55:51 +00:00
async update(ctx, next) {},
2021-10-31 01:44:52 +00:00
async destroy(ctx, next) {}, async destroy(ctx, next) {},
2021-10-28 14:55:51 +00:00
});
```
2021-10-31 01:44:52 +00:00
In both the Middleware Handler and the Action Handler, the current action instance is available via `ctx.action` , providing two very useful APIs.
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
- ``ctx.action.params``: Get the parameters of the action
- `ctx.action.mergeParams()` : handles merging of parameters from multiple sources
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
`ctx.action.params` has.
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
- Locate resources and actions
2021-10-28 14:55:51 +00:00
- `actionName`
- `resourceName`
- `associatedName`
2021-10-31 01:44:52 +00:00
- Locate the resource ID
2021-10-28 14:55:51 +00:00
- `resourceId`
- `associatedId`
- request query
- `filter`
- `fields`
- `sort`
- `page`
- `perPage`
2021-10-31 01:44:52 +00:00
- Other query values
2021-10-28 14:55:51 +00:00
- request body
- `values`
2021-10-31 01:44:52 +00:00
Example.
2021-10-28 14:55:51 +00:00
```ts
async function (ctx, next) {
const { resourceName, resourceId, filter, fields } = ctx.action.params;
// ...
}
```
2021-10-31 01:44:52 +00:00
`ctx.action.mergeParams()` is mainly used for merging multi-source parameters, using the `filter` parameter as an example. E.g., client request for articles created on 2021-09-15
2021-10-28 14:55:51 +00:00
```bash
GET /api/posts:list?filter={"created_at": "2021-09-15"}
```
2021-10-31 01:44:52 +00:00
Resource settings are locked to view only published posts
2021-10-28 14:55:51 +00:00
```ts
app.resource({
name: 'posts',
actions: {
list: {
2021-10-31 01:44:52 +00:00
filter: { status: 'publish' }, // Only view published posts
2021-10-28 14:55:51 +00:00
},
},
})
```
2021-10-31 01:44:52 +00:00
Permissions to view only articles you have created
2021-10-28 14:55:51 +00:00
```ts
app.use(async (ctx, next) => {
const { resourceName, actionName } = ctx.action.params;
if (resourceName === 'posts' & & actionName === 'list') {
ctx.action.mergeParams({
filter: {
created_by_id: ctx.state.currentUser.id,
},
});
}
await next();
});
```
2021-10-31 01:44:52 +00:00
We specify filter parameters within the client, resource configuration, and middleware above, and the parameters from the three sources will eventually be merged together as the final filter condition: `
2021-10-28 14:55:51 +00:00
```ts
async function list(ctx, next) {
2021-10-31 01:44:52 +00:00
// The filter obtained in the list operation
2021-10-28 14:55:51 +00:00
console.log(ctx.params.filter);
2021-10-31 01:44:52 +00:00
// filter is a special and merge
2021-10-28 14:55:51 +00:00
// {
2021-10-31 01:44:52 +00:00
// and: [
// { created_at: '2021-09-15' }
// { status: 'published' },
// { created_by_id: 1, }
// ]
2021-10-28 14:55:51 +00:00
// }
}
```
2021-10-31 01:44:52 +00:00
## Event
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Event listeners are placed before and after the execution of an action, and can be added via `app.db.on()` and `app.on()` . The difference is that
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
- `app.db.on()` adds a database level listener
- `app.on()` adds a listener at the server application level
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Take `users:login` as an example, it is a `query` operation in the database and a `login` operation in the application. In other words, if you need to log the login operation, you have to handle it in `app.on()` .
2021-10-28 14:55:51 +00:00
```ts
2021-10-31 01:44:52 +00:00
// Triggered when User.create() is executed when creating data
2021-10-28 14:55:51 +00:00
app.db.on('users.beforeCreate', async (model) => {});
2021-10-31 01:44:52 +00:00
// Triggered when the client `POST /api/users:login`
2021-10-28 14:55:51 +00:00
app.on('users.beforeLogin', async (ctx, next) => {});
2021-10-31 01:44:52 +00:00
// Triggered when the client `POST /api/users`
2021-10-28 14:55:51 +00:00
app.on('users.beforeCreate', async (ctx, next) => {});
```
2021-10-31 01:44:52 +00:00
## Middleware
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Server Application is based on Koa, all Koa plugins (middleware) can be used directly and can be added via `app.use()` . For example
2021-10-28 14:55:51 +00:00
```ts
const responseTime = require('koa-response-time');
app.use(responseTime());
app.use(async (ctx, next) => {
await next();
});
```
2021-10-31 01:44:52 +00:00
Slightly different from `koa.use(middleware)` , `app.use(middleware, options)` has an additional options parameter that can be used to qualify the resource and action, as well as to control where the middleware is inserted.
2021-10-28 14:55:51 +00:00
```ts
import { middleware } from '@nocobase/server';
app.use(async (ctx, next) => {}, {
name: 'middlewareName1',
2021-10-31 01:44:52 +00:00
resourceNames: [], // acts on all actions within the resource
2021-10-28 14:55:51 +00:00
actionNames: [
2021-10-31 01:44:52 +00:00
'list', // all list actions
'users:list', // List action for users resource only,
2021-10-28 14:55:51 +00:00
],
insertBefore: '',
insertAfter: '',
});
```
2021-10-31 01:44:52 +00:00
## CLI
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Application can be a CLI (with built-in Commander) in addition to being an HTTP Server. The current built-in commands are
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
- `init` initialize
- `db:sync --force` to configure synchronization with the database table structure
- `start --port` to start the application
- `plugin:**` Plugin-related
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Customization.
2021-10-28 14:55:51 +00:00
```ts
app.command('foo').action(async () => {
2021-10-31 01:44:52 +00:00
console.log('foo...') ;
2021-10-28 14:55:51 +00:00
});
```
2021-10-31 01:44:52 +00:00
## Plugin
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Above, the core extension interfaces are described, including but not limited to.
2021-10-28 14:55:51 +00:00
- Database/Collection
2021-10-31 01:44:52 +00:00
- `app.db` database instance
- `app.collection()` is equivalent to `app.db.collection()`
2021-10-28 14:55:51 +00:00
- Resource/Action
2021-10-31 01:44:52 +00:00
- `app.resource()` is the same as `app.resourcer.define()`
- `app.actions()` is the same as `app.resourcer.registerActions()`
2021-10-28 14:55:51 +00:00
- Hook/Event
2021-10-31 01:44:52 +00:00
- `app.on()` Add a server listener
- `app.db.on()` Add a database listener
2021-10-28 14:55:51 +00:00
- Middleware
2021-10-31 01:44:52 +00:00
- `app.use()` Add middleware
2021-10-28 14:55:51 +00:00
- CLI
2021-10-31 01:44:52 +00:00
- `app.cli` commander instance
- `app.command()` is equivalent to `app.cli.command()`
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Based on the above extension interface, further modular and pluggable plugins are provided, which can be added via `app.plugin()` . The process of plugins includes install, upgrade, activate, load, disable, uninstall, and the unwanted process can be missing. For example.
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
**the simplest plugin**
2021-10-28 14:55:51 +00:00
```ts
app.plugin(function pluginName1() {
});
```
2021-10-31 01:44:52 +00:00
Plugins added in this way will load directly, no installation required.
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
**JSON style**
2021-10-28 14:55:51 +00:00
```ts
const plugin = app.plugin({
2021-10-31 01:44:52 +00:00
enable: false, // default is true, you can disable it if you don't need to enable it.
2021-10-28 14:55:51 +00:00
name: 'plugin-name1',
2021-10-31 01:44:52 +00:00
displayName: 'plugin-name',
2021-10-28 14:55:51 +00:00
version: '1.2.3',
dependencies: {
pluginName2: '1.x',
pluginName3: '1.x',
},
async install() {},
async upgrade() {},
async activate() {},
async bootstrap() {},
async deactivate() {},
async unstall() {},
});
2021-10-31 01:44:52 +00:00
// Activate the plugin via the api
2021-10-28 14:55:51 +00:00
plugin.activate();
```
2021-10-31 01:44:52 +00:00
**OOP style**
2021-10-28 14:55:51 +00:00
```ts
class MyPlugin extends Plugin {
async install() {}
async upgrade() {}
async bootstrap() {}
async activate() {}
async deactivate() {}
async unstall() {}
}
app.plugin(MyPlugin);
2021-10-31 01:44:52 +00:00
// or
2021-10-28 14:55:51 +00:00
app.plugin({
name: 'plugin-name1',
2021-10-31 01:44:52 +00:00
displayName: 'plugin-name',
2021-10-28 14:55:51 +00:00
version: '1.2.3',
dependencies: {
pluginName2: '1.x',
pluginName3: '1.x',
},
plugin: MyPlugin,
});
```
2021-10-31 01:44:52 +00:00
**Reference to a separate Package**
2021-10-28 14:55:51 +00:00
```ts
app.plugin('@nocobase/plugin-action-logs');
```
2021-10-31 01:44:52 +00:00
Plugin information can also be written directly in `package.json`
2021-10-28 14:55:51 +00:00
```js
{
name: 'pluginName1',
2021-10-31 01:44:52 +00:00
displayName: 'pluginName',
2021-10-28 14:55:51 +00:00
version: '1.2.3',
dependencies: {
pluginName2: '1.x',
pluginName3: '1.x',
},
}
```
2021-10-31 01:44:52 +00:00
**Plugins CLI**
2021-10-28 14:55:51 +00:00
```bash
plugin:install pluginName1
plugin:unstall pluginName1
plugin:activate pluginName1
plugin:deactivate pluginName1
```
2021-10-31 01:44:52 +00:00
Currently available plugins.
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
- @nocobase/plugin -collections provides data table configuration interface to manage data tables via HTTP API.
- @nocobase/plugin -action-logs Action logs
- @nocobase/plugin -automations Automation (not upgraded to v0.5, not available yet)
- @nocobase/plugin -china-region China Administrative Region
- @nocobase/plugin -client provides a client-side, codeless visual configuration interface that needs to be used in conjunction with @nocobase/client
- @nocobase/plugin -export exports
- @nocobase/plugin -file-manager File manager
- @nocobase/plugin -permissions Roles and permissions
- @nocobase/plugin -system-settings System configuration
- @nocobase/plugin -ui-router Front-end routing configuration
- @nocobase/plugin -ui-schema ui configuration
- @nocobase/plugin -users user module
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
## Testing
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
If you have code, you need to test it. @nocobase/test provides mockDatabase and mockServer for database and server testing, e.g.
2021-10-28 14:55:51 +00:00
```ts
import { mockServer, MockServer } from '@nocobase/test';
describe('mock server', () => {
let api: MockServer;
beforeEach(() => {
api = mockServer({
dataWrapping: false,
});
api.actions({
list: async (ctx, next) => {
ctx.body = [1, 2];
await next();
},
});
api.resource({
name: 'test',
});
});
afterEach(async () => {
return api.destroy();
});
it('agent.get', async () => {
const response = await api.agent().get('/test');
expect(response.body).toEqual([1, 2]);
});
it('agent.resource', async () => {
const response = await api.agent().resource('test').list();
expect(response.body).toEqual([1, 2]);
});
});
```
2021-10-31 01:44:52 +00:00
## Client
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
To allow more non-developers to participate, NocoBase provides a companion client plugin - a visual configuration interface without code. The client plugin needs to be used in conjunction with @nocobase/client and can be used directly or modified by yourself.
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Plugin configuration
2021-10-28 14:55:51 +00:00
```ts
app.plugin('@nocobase/plugin-client', {
2021-10-31 01:44:52 +00:00
// Customize the dist path
dist: path.resolve(__dirname, '. /node_modules/@nocobase/client/app'),
});
```
To meet the needs of various scenarios, the client `@nocobase/client` provides a rich set of basic components.
- Action - Action
- Window Open in the current browser window/tab
- Drawer opens the drawer (default right-hand drawer)
- Action.Modal Open dialog box
- Dropdown Dropdown menu
- Popover Bubble card
- Action.Group Grouping of buttons
- Action.Bar Action Bar
- AddNew "Add" module
- AddNew.CardItem - add a block
- AddNew.PaneItem - add block (view panel, related to currently viewed data)
- AddNew.FormItem - add fields
- BlockItem/CardItem/FormItem - decorators
- BlockItem - normal decorator (no wrapping effect)
- CardItem - card decorator
- FormItem - field decorator
- Calendar - Calendar
- Cascader - Cascade selection
- Chart - Chart
- Checkbox - Checkboxes
- Checkbox.Group - Multiple checkboxes
- Collection - Data Table Configuration
- Collection.Field - Data table fields
- ColorSelect - color selector
- DatePicker - date picker
- DesignableBar - Configuration Toolbar
- Filter - filter
- Form - Form
- Grid - Grid layout
- IconPicker - Icon selector
- Input - input box
- TextArea - Multi-line input box
- InputNumber - Number box
- Kanban - Kanban board
- ListPicker - list picker (for selecting and displaying associated data)
- Markdown editor
- Menu - Menu
- Password - password
- Radio - radio box
- Select - selector
- Table - Table
- Tabs - Tabs
- TimePicker - time picker
- Upload - Upload
You can extend the component by yourself, the above component is built based on Formily, how to customize the component you see the related component source code or Formily documentation, here is something different.
- How to extend the database fields?
- How to add third party blocks to the AddNew module?
- How to add more built-in actions to the action bar?
- How can I customize the configuration toolbar?
In addition to the components having flexible extensions, the client can also be used in any front-end framework to customize Request and Router, e.g.
2021-10-28 14:55:51 +00:00
< pre lang = "tsx" >
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { ClientSDK, Application } from '@nocobase/client';
2021-10-31 01:44:52 +00:00
// Initialize the client instance
2021-10-28 14:55:51 +00:00
const client = new ClientSDK({
request: (options) => Promise.resolve({}),
});
2021-10-31 01:44:52 +00:00
// Adapting the Route Component
2021-10-28 14:55:51 +00:00
const RouteSwitch = createRouteSwitch({
components: {
AdminLayout,
AuthLayout,
RouteSchemaRenderer,
},
});
ReactDOM.render(
< ClientProvider client = {client} >
< MemoryRouter initialEntries = {['/admin']} >
< RouteSwitch routes = {[]}/ >
2021-10-31 01:44:52 +00:00
< /MemoryRouter
2021-10-28 14:55:51 +00:00
< / ClientProvider > ,
document.getElementById('root'),
);
< / pre >
2021-10-31 01:44:52 +00:00
For more details, you can initialize the project scaffolding and experience it via `create-nocobase-app` .
2021-10-28 14:55:51 +00:00
```bash
yarn create nocobase-app my-nocobase-project
```
2021-10-31 01:44:52 +00:00
By default, nocobase-app uses umijs as the project builder and integrates Server as the data interface. The initialized directory structure is as follows
2021-10-28 14:55:51 +00:00
```bash
|- src
|- pages
|- apis
|- .env
|- .umirc.ts
|- package.json
```
2021-10-31 01:44:52 +00:00
## Cases
2021-10-28 14:55:51 +00:00
2021-10-31 01:44:52 +00:00
Small MIS with full front and back ends.
2021-10-28 14:55:51 +00:00
< img src = "../../images/MiniMIS.png" style = "max-width: 300px; width: 100%;" >
2021-10-31 01:44:52 +00:00
API service with no client, providing a pure back-end interface.
2021-10-28 14:55:51 +00:00
< img src = "../../images/API.png" style = "max-width: 280px; width: 100%;" >
2021-10-31 01:44:52 +00:00
Applet + Backend admin, only one set of database, but two sets of users and permissions, one for backend users and one for applet users.
2021-10-28 14:55:51 +00:00
< img src = "../../images/MiniProgram.png" style = "max-width: 600px; width: 100%;" >
2021-10-31 01:44:52 +00:00
SaaS service (shared user), each application has its own supporting database and the data of each application is completely isolated. Applications don't need user and permission modules, SaaS master is shared globally now.
2021-10-28 14:55:51 +00:00
< img src = "../../images/SaaS2.png" style = "max-width: 450px; width: 100%;" >
2021-10-31 01:44:52 +00:00
SaaS service (independent user), each app has its own independent user module and permissions, and the app can be bound to its own domain.
2021-10-28 14:55:51 +00:00
< img src = "../../images/SaaS1.png" style = "max-width: 450px; width: 100%;" >