Client Code Generation
Assume you are developing a web app, the back-end team publishes an api and shares a json file for their api specification.
Example Case
Let's say the back-end team shared the following schema;
SCHEMA: photos.service.json
{
"/photos": {
"post": {
"content": "base64",
"response": {
"model": "#/ref"
}
},
"get": {
"start": "date",
"end": "date",
"response": {
"model": "#/photo",
"type": "array"
}
}
},
"definitions": {
"ref": {
"uid": "string"
},
"photo": {
"uid": "string",
"url": "url",
"date": "date"
}
}
}
This schema is not OpenAPI, because of demonstration purposes.
Now to consume this api from your web app, assume you need to write below code;
CODE: photos.client.js
function photos(url) {
function post(content) {
return axios(
method: "post",
url: `${url}/photos`,
...parameters({
content: content
})
).then(response => response);
}
function get(start, end) {
return axios(
method: "get",
url: `${url}/photos`,
...parameters({
start: start,
end: end
})
).then(response => response);
}
function parameters(method, parameters) {
return method === "get"
? { params: parameters }
: { data: parameters };
}
return Object.freeze({
post: post,
get: get
});
}
With Concepts
Here, json concepts can help you parse and structure given service schema, so that you can use a template engine to generate necessary client code.
Below concepts definition will cover given api schema;
CONCEPTS: service.concepts.json
{
"$resource*": {
"$method*": {
"$parameter*": "$type",
"!response": {
"model": "$model",
"type?": "$type"
}
}
},
"definitions": {
"$definition*": {
"$property*": "$type"
}
}
}
Now that you have defined every concept for the given api schema, we can make use of them to generate code with the following template;
TEMPLATE: client.template.js
/* #resource */
function $resource$(url) {
/* #method */
function $method$(/* #, */$parameter$) {
return axios(
method: "$method$",
url: `${url}/$resource$`,
...parameters("$method$", {
$parameter$: $parameter$ // #parameter,
})
).then(response => response);
}
/* / */
function parameters(method, parameters) {
return method === "get"
? { params: parameters }
: { data: parameters };
}
return Object.freeze({
$method$: $method$ // #method,
});
}
/* / */
We used codestache template syntax because of its readability. Below you can find a mustache version of the same code template.
Mustache version
{{#resource}} function {{resource}}(url) { {{#method}} function {{method}}({{#parameter}}{{parameter}}{{^last}},{{/last}}{{/parameter}}) { return axios( method: "{{method}}", url: `${url}/{{resource}}`, ...parameters("{{method}}", { {{#parameter}} {{parameter}}: {{parameter}}{{^last}},{{/last}} {{/parameter}} }) ).then(response => response); } {{/method}} function parameters(method, parameters) { return method === "get" ? { params: parameters } : { data: parameters }; } return Object.freeze({ {{#method}} {{method}}: {{method}}{{^last}},{{/last}} {{/method}} }); } {{/resource}}
Note that mustache does not support
last
and it does not exist in the shadow of above schema. Solast
needs to be added to schema shadow for this template to work correctly.
Now that its concepts and template are ready, client code can be generated during build time with the following script;
const schema = jc.Schema.load('photos.service.json', 'service.concepts.json');
const photosClient = cs.render('client.template.js', schema.shadow);
fs.writeFile('photos.client.js', photosClient);
Without Concepts
Without using code generation, you would simply write the above code manually and maintain it for future updates of the back-end api. There are a couple of drawbacks worth mentioning here.
Syncing With New Services
You will maintain your client code manually with every back-end update. With code generation you will have the implementation right away, without any errors.
Lower Cost of Change
Since it is generated, you can change your design decisions to make it better at any time. For example, if you want to change axios
to fetch
, it would require a rewrite for the client layer without code generation. Another example, you may want to change the way you handle errors. If you use code generation, it won't cost you an extra.
More Readable
Things like error handling, logging and authentication are not necessarily have to be done through runtime mechanisms like interceptors. This is because it is not considered as code duplication when you generate the code.
More Things To Generate
You can make use of this schema to generate mock implementations and test data as well. This way you don't have to wait for the back-end team to develop these apis.