One of the issues I encounter when building client/server applications is that model classes are often duplicated on both sides. In the past I have used feathers.js which solved this problem by providing feathers-client
, a client-side counterpart to the server implementation. More recently I am looking into IBM’s Loopback framework which has built-in support for auto-generated OpenAPI specifications available on the /openapi.json
endpoint. More on this later.
For the client, I started off with a create-react-app
template and added the Ant.Design UI framework on top.
I originally wanted to use fetch
to handle all API related communication but quickly realised the amount of boilerplate I would have to write out by hand. (basically writing out an API client library by hand).
Introducing openapi-client-axios, a Javascript client generator that will take care of all that for you.
All I had to do was overwrite the operationId
in the Loopback server code. This is because the framework generates Ids that are not valid method names (they contain a space). This is a bit of an oversight in their implementation, but fixing it is not a big deal.
I simply added “operationId” below the responses
object in each controller action and the generated openapi.json
was updated accordingly.+
@get('/accounts', {
"operationId" : "getAccounts",
responses: {
'200': {
description: 'Array of Account model instances',
content: {
'application/json': {
schema: {
type: 'array',
items: getModelSchemaRef(Account, {includeRelations: true}),
},
},
},
},
}
})
async find(
@param.query.object('filter', getFilterSchemaFor(Account)) filter?: Filter,
): Promise {
return this.accountRepository.find(filter);
}
To set up OpenAPIClientAxios in my react project, I created a new file api.js
with the following content:
const api = new OpenAPIClientAxios({ definition: 'http://localhost:3000/openapi.json' });
api.init();
async function getAccounts() {
const client = await api.getClient();
console.log(client)
const res = await client.getAccounts();
console.log('Accounts queried: ', res.data);
}
createTodo().then(r => console.log(r))
This initialises the Client and retrieves all accounts before printing them to the console.
I am really happy with this setup because it allows me to consume the API without writing boilerplate code.