Liam
30 Oct, 2018
Make REST api to partial response, just like GraphQL
The story began in one day, I want to make NodeJS API endpoints like
GET /api/blogposts?fields=author{name,avatar},title,detail
and it should return me
[{
"author": {
"name": "Liam Ng",
"avatar": "https://someurl",
},
"title": "blogpost title",
"detail": "blogpost detail"
}]
When using GraphQL, partial response is a build in tool. However sometimes REST is a more suitable use case.
Searching the web, I do find some libraries that do similar things however the feature is somewhat limited. Developers should not be in situation that
- want to implement partial response
- not so supported in REST
- use GraphQL
Instead, REST should be able to achieve it as easily, with a library.
Things to acknowledge
- Graphql still provide you a better way to do concurrent GET request (though the code might get complicated on retry on partial error)
- It should be build with somethings like swagger to let user knows available fields (it do comes with handy function to return response schema in OpenAPI 3 format)
Introduce partial-responsify
tl;dr: you can find the code at github
Using it should be simple
import { PartialResponsify, ResponseFormat } from "partial-responsify";
// ideally should be inside DI
const pr = new PartialResponsify();
// it is normally inside ctx.query for Koa
// default don't support whitespace or - or _
// (normally should be camelcase?)
const fields = "name,coords,author{name{first}}";
const responseFormat: ResponseFormat = {
fields: {
author: {
fields: {
name: {
fields: {
first: {
type: "string",
},
last: {
type: "string",
},
},
type: "object",
},
},
type: "object",
},
coords: {
items: {
items: {
type: "number",
},
type: "array",
},
type: "array",
},
license: {
type: "string",
},
name: {
type: "string",
},
},
type: "object",
};
// you can use this fieldsToParse to track the fields usage
const fieldsToParse = pr.parseFields(fields, responseFormat);
console.log(fieldsToParse);
// and then you perform some logic and got the result
const result = {
author: {
name: {
first: "Liam",
last: "Ng",
},
url: "https://www.leliam.com",
},
coords: [[13.37, 1.337], [0, 0]],
license: "MIT",
name: "partial-responsify",
};
const res = pr.parseResult<any>(fieldsToParse, responseFormat, result);
console.log(res);
/*
the result should be:
[ [ [], 'name' ],
[ [], 'coords' ],
[ [ 'author', 'name' ], 'first' ] ]
{ author: { name: { first: 'Liam' } },
coords: [ [ 13.37, 1.337 ], [ 0, 0 ] ],
name: 'partial-responsify' }
*/
Since we use responseFormat
to validate the response, it will be great if there is a function to generate swagger response schema based on it!
import { ResponseFormat, SchemaGenerator } from "partial-responsify";
const sgen = new SchemaGenerator();
const responseFormat: ResponseFormat = {
items: {
fields: {
a: {
type: "number",
},
b: {
type: "string",
},
c: {
type: "integer",
},
d: {
format: "uuid",
type: "string",
},
e: {
fields: {
a: {
type: "number",
},
},
type: "object",
},
},
type: "object",
},
type: "array",
};
const result = sgen.generate(responseFormat);
console.log(result);
/*
{
items: {
properties: {
a: {
type: "number",
},
b: {
type: "string",
},
c: {
type: "integer",
},
d: {
format: "uuid",
type: "string",
},
e: {
properties: {
a: {
type: "number",
},
},
type: "object",
},
},
type: "object",
},
type: "array",
}
*/
To make it better
It will require a library to auto generate Swagger JSON file to make it shine. It is not hard to make the library however will be large amount of works to support every edge case.