fib-app
fibjs application basic api framework
Install
1npm install fib-app [--save]
Test
1npm test
Create basic script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const http = require('http');
const util = require('util')
const Session = require('fib-session')
const App = require('../');
var app = new App('sqlite:test.db', {
uuid: true
});
app.db.use(require('./defs/person'));
var session = new Session(new util.LruCache(20000), {
timeout: 60 * 1000
});
var svr = new http.Server(8080, [
session.cookie_filter,
{
'/1.0': app
}
]);
svr.run();
Where person
is the Model definition module, the content is as follows:
1
2
3
4
5
6
7module.exports = db => {
db.define('person', {
name: String,
sex: ["male", "female"],
age: Number
});
};
This is a standard ORM definition, and you can also use other ORM functions, such as type checking, events, etc.
API data format
For POST and PUT requests, the request body must be in JSON format, and the Content-Type of the HTTP header needs to be set to application/json.
1
2
3
4curl -X PUT \
-H "Content-Type: application/json" \
-d '{"name": "tom","sex":"male","age":23}' \
http://localhost/1.0/person/57fbbdb0a2400000
For all requests, the response format is a JSON object.
Whether a request was successful or not is indicated by the HTTP status code. A 2XX status code indicates success, while a 4XX status code indicates that the request failed. When a request fails, the body of the response is still a JSON object, but it always contains the code and message fields, which you can use for debugging. For example, if a request for permission authentication fails, the following information will be returned:
1
2
3
4{
"code": 4030501,
"message": "The operation isn’t allowed for clients due to class-level permissions."
}
The code code is divided into three parts. The first three digits 403 represent the error type, 05 represents the data sheet number, and 01 represents the detailed error code.
For GET requests, object data is usually returned. Depending on the address of the GET request, an object or an array may be returned. for example:
1
2
3
4
5{
"name": "tom",
"sex": "male",
"age": 23
}
or:
1
2
3
4
5
6
7
8
9
10
11
12[
{
"name": "tom",
"sex": "male",
"age": 23
},
{
"name": "lily",
"sex": "female",
"age": 22
}
]
special fields
In the object data, there are four fields with special meanings that are not allowed to be changed through the API. They are id
, updatedAt
, createdAt
, respectively createdBy
.
Among them id
, updatedAt
, createdAt
single fields will be automatically created and modified. createdBy
You need to specify the type yourself.
Basic object access API
After completing such data definition, you will directly have a set of interface calls that comply with the REST api specification:
url | method | action |
---|---|---|
/1.0/:className | POST | Create new object |
/1.0/:className/:id | GET | Read object |
/1.0/:className/:id | PUT | Modify object |
/1.0/:className/:id | DELETE | Delete object |
/1.0/:className | GET | Query object list |
Create new object
To create a new object, a POST request should be sent to the class's URL, which should include the object itself. For example, to create the object shown above:
1
2
3
4curl -X POST \
-H "Content-Type: application/json" \
-d '{"name": "tom","sex":"male","age":23}' \
http://localhost/1.0/person
When the creation is successful, the HTTP return is 201 Created, and the body of the response is a JSON object containing the objectId and createdAt timestamp of the new object:
1
2
3
4{
"createdAt": "2017-11-25T01:39:35.931Z",
"id": "57fbbdb0a2400000"
}
Read object
When you create an object, you can retrieve its contents by sending a GET request to the Location in the returned header. For example, to get the object we created above:
1curl -X GET http://localhost/1.0/person/57fbbdb0a2400000
The returned body is a JSON object containing all user-supplied fields plus the createdAt
, updatedAt
and id
fields:
1
2
3
4
5
6
7
8{
"name": "tom",
"sex": "male",
"age": 23,
"createdAt": "2017-11-25T01:39:35.931Z",
"updatedAt": "2017-11-25T01:39:35.931Z",
"id": "57fbbdb0a2400000"
}
By setting the return field keys
, you can customize the returned content, keys
which is a ,
string of field names separated by:
1curl -X GET http://localhost/1.0/person/57fbbdb0a2400000?keys=name%2Csex
will return:
1
2
3
4{
"name": "tom",
"sex": "male"
}
Modify object
In order to change an object's existing data, you can send a PUT request to the corresponding URL of the object. Any keys you do not specify will not be changed, so you can only update a subset of the object's data. For example, let's change an age field of our object:
1
2
3
4curl -X PUT \
-H "Content-Type: application/json" \
-d '{"age": 25}' \
http://localhost/1.0/person/57fbbdb0a2400000
The returned JSON object will contain updatedAt
and id
fields indicating when the update occurred:
1
2
3
4{
"updatedAt": "2017-11-25T01:39:35.931Z",
"id": "57fbbdb0a2400000"
}
Delete object
To delete an object, you can send a DELETE request to the specified object's URL, for example:
1curl -X DELETE http://localhost/1.0/person/57fbbdb0a2400000
Query object list
You can get multiple objects at once by sending a GET request to the class's URL, without any URL parameters. Here's how to simply get all users:
1curl -X GET http://localhost/1.0/person
The returned value is a JSON object containing the results field, whose value is a list of objects:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18[
{
"name": "tom",
"sex": "male",
"age": 23,
"createdAt": "2017-11-25T01:39:35.931Z",
"updatedAt": "2017-11-25T01:39:35.931Z",
"id": "57fbbdb0a2400000"
},
{
"name": "lily",
"sex": "female",
"age": 22,
"createdAt": "2017-11-25T01:39:35.931Z",
"updatedAt": "2017-11-25T01:39:35.931Z",
"id": "57fbbdb0a2400001"
}
]
keys field customization
Like object query, you can keys
customize the fields included in the returned results by setting when querying the list. keys
The content is a ,
string of field names separated by , for example:
1curl -X GET http://localhost/1.0/person?keys=name%2Cage
Will specify that only the name
and age
fields will be returned.
where filter condition
where
The query object can be constrained in the form of parameters.
where
The parameter value should be JSON encoded. That is, if you look at the actual URL request being made, it should be JSON-encoded first and then URL-encoded. The simplest where
way to use parameters is to include the appropriate key and value. For example, if we wanted to search for users named tom, we would construct the query like this:
1curl -X GET http://localhost/1.0/person?where=%7B%22name%22%3A%22tom%22%7D
where
The value is a urlencoded JSON string, the content is:{"name":"tom"}
In addition to matching a given value exactly, where
comparison methods such as inclusion are also supported. where
Parameters support the following options:
key | operation | sample |
---|---|---|
eq | equal | {"name":{"eq":"tom"}} or {"name":"tom"} |
ne | not equal to | {"name":{"ne":"tom"}} |
gt | more than the | {"age":{"gt":"24"}} |
gte | greater or equal to | {"age":{"gte":"24"}} |
lt | less than | {"age":{"lt":"24"}} |
lte | less than or equal to | {"age":{"lte":"24"}} |
like | fuzzy query | {"name":{"like":"%m"}} |
not_like | fuzzy query | {"name":{"not_like":"%m"}} |
between | Interval comparison | {"age":{"between":[22,25]}} |
not_between | Interval comparison | {"age":{"not_between":[22,25]}} |
in | enumerate | {"name":{"in":["tom","lily"]}} |
not_in | enumerate | {"name":{"not_in":["tom","lily"]}} |
or | OR operation | {"or":[{"name":"tom"},{"age":24}]} |
skip skip records
Through skip
the option, you can skip the specified number of records to achieve the effect of page turning.
1curl -X GET http://localhost/1.0/person?skip=100
limit returns record limit
Through limit
the option, you can limit the number of records returned. limit
The valid numbers are 1-1000, and the default is 100.
1curl -X GET http://localhost/1.0/person?limit=100
order specifies the sorting method
Use order
the option to set the sorting method of the returned result set. When the field name contains before it, -
it is in reverse order.
1curl -X GET http://localhost/1.0/person?order=-id
count returns the total number of results
Incremented on request count
The total number of resultsets that can be returned while returning the specified content.
1curl -X GET http://localhost/1.0/person?count=1&limit=1
At this time, the returned result will contain two fields: count
and results
, containing the total number and result respectively:
1
2
3
4
5
6
7
8
9
10
11
12
13{
"count": 2,
"results": [
{
"name": "tom",
"sex": "male",
"age": 23,
"createdAt": "2017-11-25T01:39:35.931Z",
"updatedAt": "2017-11-25T01:39:35.931Z",
"id": "57fbbdb0a2400000"
}
]
}
Create extension object
By defining hasOne and hasMany through ORM, you can define the association between objects and reflect it on the API, for example:
1
2
3
4
5
6
7
8
9module.exports = db => {
var Person = db.models.person;
var Pet = db.define('pet', {
name: String
});
Person.hasMany('pets', Pet);
};
Extended object access API
The following is the API definition of the extension object:
url | method | action |
---|---|---|
/1.0/:className/:id/:extendName | PUT | Set extension object |
/1.0/:className/:id/:extendName | POST | Create extension object |
/1.0/:className/:id/:extendName/:rid | GET | Read extension object |
/1.0/:className/:id/:extendName/:rid | PUT | Modify extension object |
/1.0/:className/:id/:extendName/:rid | DELETE | Delete extended object |
/1.0/:className/:id/:extendName | GET | Query extended object list |
Set extension object
Setting an extension object is to establish a relationship between two independent objects. For example, if Tom adopts a pet named cat, he can use the following operations to achieve this:
1
2
3
4curl -X PUT \
-H "Content-Type: application/json" \
-d '{"id": "57fbbdb0a2400007"}' \
http://localhost/1.0/person/57fbbdb0a2400000/pets
In the call, the cat's id needs to be specified in the body.
Create extension object
Directly creating extended objects can establish connections between objects while creating objects. for example:
1
2
3
4curl -X POST \
-H "Content-Type: application/json" \
-d '{"name": "cat"}' \
http://localhost/1.0/person/57fbbdb0a2400000/pets
A pet named cat will be created and associated with tom.
Read extension object
Reading extended objects is very similar to reading base objects, and also supports the keys option:
1curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007
Modify extension object
Reading extended objects is very similar to reading base objects:
1
2
3
4curl -X PUT \
-H "Content-Type: application/json" \
-d '{"name": "cat 1"}' \
http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007
Delete extended object
Deleting an extended object does not delete the object itself, it only dissolves the relationship between the objects:
1curl -X DETELE http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007
Query extended object list
Querying the extended object list is very similar to querying the basic object list, and also supports options such as keys and conditional filtering:
1curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets
ACL
Data permissions can be controlled by defining the Model's ACL. for example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25const orm = require('fib-orm');
module.exports = db => {
db.define('blog', {
title: String,
detail: String,
note: String
}, {
ACL: function(session) {
return {
"*": {
"*": false
},
"57fbbdb0a2400000": {
"*": true
},
"roles": {
"user": {
"read": true
}
}
};
}
});
};
If no ACL is specified when defining the Model, it is equivalent to setting the default permissions:
1
2
3
4
5{
"*": {
"*": true
}
}
main body
There are three types of ACL subject descriptions: user id
, user role
and *
, id
which represent a specific user, role
represent users with a certain role, and *
represent all users:
main body | describe | priority |
---|---|---|
ID | specific user id | 1 |
role | User group name | 2 |
* | All | 3 |
When checking permissions, it will first match id
the corresponding permissions. If not specified, then match role
the corresponding permissions of the user. If it is still specified, check whether *
the permissions of are specified. If *
not specified, there is no permission.
For example, in the above permission configuration, user
the user group is specified to be able to read. The user 57fbbdb0a2400000
has all permissions, but other users do not have any permissions.
Permissions
ACL classifies permissions into five categories based on API behavior:
Permissions | describe | allowed types |
---|---|---|
create | Create object | true/false/array |
read | Read object | true/false/array |
write | Modify object | true/false/array |
delete | Delete object | true/false |
find | Query object list | true/false |
* | Match all permissions | true/false/array |
Permissions are set true
to allow access, false
to deny access, and array
to only allow access to specified fields. delete
and find
are not accepted and are treated the same array
if set . If the specified permission does not exist, the permissions under the same subject will be matched. If neither exists, query the subject of the next priority level again.array
true
*
For example, in the above example, if you need to set user
only read title
and detail
allow others to read title
, you can set it like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14{
"*": {
"*": false,
"read": ['title']
},
"57fbbdb0a2400000": {
"*": true
},
"roles": {
"user": {
"read": ['title', 'detail']
}
}
}
Object permissions
The permissions set on the Model are the permissions of the entire class. If you need to set permissions on specific objects, you can do so by setting OACL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24module.exports = db => {
db.define('person', {
name: String,
sex: ["male", "female"],
age: Number
}, {
ACL: function(session) {
return {
"*": {
"*": false
}
}
},
OACL: function(session) {
var _acl = {};
if(this.id === session.id)
_acl[session.id] = {
"*": true
};
return _acl;
}
});
};
In this example, when the visitor is the object himself, all operations will be allowed, otherwise all access will be prohibited. Permissions are checked as follows:
person[57fbbdb0a2400000]
=>OACL
person
=>ACL
Extended object permissions
The access permission control of extended objects is similar to the permissions of basic objects. The only difference is that the ACL needs to be specified separately:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41module.exports = db => {
var Person = db.define('person', {
name: String,
sex: ["male", "female"],
age: Number
},{
ACL: function(session) {
return {
"*": {
"read": ['name', 'sex'],
"extends": {
"pets": {
"read": true,
"find": true
}
}
}
}
},
OACL: function(session) {
var _acl = {};
if(this.id === session.id)
_acl[session.id] = {
"*": true,
"extends": {
"pets": {
"*": true
}
}
};
return _acl;
}
});
var Pet = db.define('pet', {
name: String
});
Person.hasMany('pets', Pet);
};
In this definition, anyone can check the name
sum of personal information sex
, and freely check and search it pets
. The user himself can operate all his own data and have full permissions for his own pet information.
When checking access permissions for extended objects, object permissions and extended object permissions are checked separately. For example, the following request:
1curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007
Permissions are checked as follows:
pets[57fbbdb0a2400007]
=>OACL
person[57fbbdb0a2400000]
=>OACL
=>extends
=>pets
person
=>ACL
=>extends
=>pets
pets
=>ACL
Function
APIs can be defined for the Model, and complex data operations can be completed through custom Functions.
Most permissions can be controlled through ACL, and object-based permissions do not need to be implemented through Function. Function can be used to complete data-based permissions, such as granting permissions to different user groups based on approval status. And multiple modifications, such as the need to modify multiple database records.
Draw data model
After completing the data definition, you can use to app.diagram()
draw the format class diagram of the data model svg
. When saving to a file, you will get an image similar to the following: