Generate your data SDK with Nx
With my @trumbitta/nx-plugin-openapi, to be precise
💡 Heads up! You can find the code for every article in this series on GitHub, with a tag per article: github.com/trumbitta/giant-robots/tags
So far we have two backend apps, one frontend app, and a couple libs one of which is for models shared across all the apps.
Then something happens: we need a new endpoint to get the data for one giant robot by id
and... wait, by id
? Our GiantRobot
model doesn't have an id
.
Now we need to:
- Update the existing endpoint so that it doesn't just respond to the quick and dirty
/api
, but from now on it will respond to a more appropriate/api/v1/giant-robots
- Add the new
/api/v1/giant-robots/:id
endpoint - Add the
id
property to theGiantRobot
model - Use the new API endpoints
That's some boring work. And it could happen again next week.
If only there was a robust way to automate half of it!
PermalinkOh but there is
I created the Nx plugin for OpenAPI precisely because I didn't want to deal with this kind of boring, repetitive, work.
Let's think about the situation: the first three points above are three business decisions (or two, if we consider adding the id
property a technical consequence). Business decisions don't belong in code, they belong in documentation.
Fortunately, the OpenAPI Specification aka Swagger 3 is the exact kind of documentation for business decisions about your API layer.
PermalinkInstalling the plugin and creating the OpenAPI spec file
Open a terminal inside your workspace and run:
npm i -D @trumbitta/nx-plugin-openapi
Now let's generate a api-spec
library to host the OpenAPI spec file we are going to write:
nx generate @trumbitta/nx-plugin-openapi:api-spec --name=api-spec
And these should be the options and output:
✔ Do you want me to also create a sample spec file for you? (y/N) · false
UPDATE package.json
UPDATE workspace.json
UPDATE nx.json
CREATE libs/api-spec/src/.gitkeep
[...]
Now create libs/api-spec/src/giant-robots.openapi.yml
and paste the following content in:
openapi: 3.0.3
info:
title: Giant Robots
version: 1.0.0
servers:
- url: http://localhost:4200/api/v1
tags:
- name: giant-robots
description: Everything about giant robots
paths:
/giant-robots:
get:
tags:
- giant-robots
summary: Get all the giant robots
operationId: getGiantRobots
responses:
200:
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/GiantRobot'
/giant-robots/{id}:
get:
tags:
- giant-robots
summary: Find giant robot by ID
description: Returns a single pet
operationId: getGiantRobotById
parameters:
- name: id
in: path
description: ID of giant robot to return
required: true
schema:
type: string
responses:
200:
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/GiantRobot'
400:
description: Invalid ID supplied
content: {}
404:
description: Giant robot not found
content: {}
components:
schemas:
GiantRobot:
required:
- id
- name
- height
- weight
type: object
properties:
id:
type: string
name:
type: string
example: Mazinger Z
height:
type: integer
example: 18
weight:
type: integer
example: 20
And this is a preview you can get if you paste it on editor.swagger.io
PermalinkReplacing the shared models library with a shared API library
This part of the plugin uses @openapitools/openapi-generator-cli
and it needs a JVM to work.
You can install Java from the official site, or if you are using Homebrew:
brew tap AdoptOpenJDK/openjdk
brew install --cask adoptopenjdk12
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-12.0.2.jdk/Contents/Home/
You can check if you have Java, or if it's configured correctly, by running:
java --version
If it outputs something meaningful, you are all set.
Enough with the pre-requisites, let's create our new shared library.
Delete the old shared models library:
nx generate @nrwl/workspace:remove --projectName=shared-models --forceRemove
Notice the --forceRemove
option because some other libs and applications still depend on the lib we just deleted.
Now let's create the new automagic api library:
nx g @trumbitta/nx-plugin-openapi:api-lib api --directory=shared
And these should be your answers and output:
✔ Which OpenAPITool generator would you like to use? (https://github.com/OpenAPITools/openapi-generator) · typescript-fetch
✔ Is the API spec file published online? (y/N) · false
✔ If it's online, what's the URL where I can get the API spec file from? ·
✔ If it's online, which authorization headers do you need to add? ·
✔ If it's local, what's the name of the lib containing the API spec file? · api-spec
✔ If it's local, what's the path of the API spec file starting from the lib root? · src/giant-robots.openapi.yml
✔ Do you want to specify any additional properties for the generator? key1=value1,key2=value2 (https://openapi-generator.tech/docs/generators) · supportsES6,typescriptThreePlus
✔ Do you want to specify any global properties for the generator? key1=value1,key2=value2 (https://openapi-generator.tech/docs/globals) ·
UPDATE workspace.json
UPDATE nx.json
CREATE libs/shared/api/README.md
CREATE libs/shared/api/.babelrc
UPDATE tsconfig.base.json
This added the following configuration to your workspace.json
:
[...]
"shared-api": {
"root": "libs/shared/api",
"sourceRoot": "libs/shared/api/src",
"projectType": "library",
"targets": {
"generate-sources": {
"executor": "@trumbitta/nx-plugin-openapi:generate-api-lib-sources",
"options": {
"generator": "typescript-fetch",
"sourceSpecPathOrUrl": "libs/api-spec/src/giant-robots.openapi.yml",
"additionalProperties": "supportsES6,typescriptThreePlus",
"globalProperties": ""
}
}
}
},
[...]
So let's execute that target and let's see what happens!
nx run shared-api:generate-sources
💥 Boom! Ready to use!
Heads up! For simplicity's sake we are not going to implement / use the
GET /api/v1/giant-robots/:id
endpoint. I'd rather keep the focus on Nx!
PermalinkUsing the shared API library for the frontend
Update apps/frontend/src/app/app.tsx
:
[...]
// Libs
import {
GiantRobotsApi,
GiantRobot,
} from '@giant-robots/shared/api';
[...]
function App() {
const { fairAdjective } = environment;
const [giantRobots, setGiantRobots] = useState<GiantRobot[]>([]);
useEffect(() => {
const fetchData = async () => {
const api = new GiantRobotsApi();
try {
const robots = await api.getGiantRobots();
setGiantRobots(robots);
} catch (error) {
console.error(error);
}
};
fetchData();
}, []);
[...]
Update libs/features/robots/src/lib/giant-robots/giant-robots-list.component.tsx
:
[...]
// Libs
import { GiantRobot } from '@giant-robots/shared/api';
[...]
But the app won't work, yet. We need to also update the backend!
PermalinkUpdating the backend
app
Now we need to account for the new GET /api/v1/giant-robots
resource request.
It would be nice to just create another lib and use a NestJS generator to scaffold most of the code.
Sadly at the time of writing the only available generator for NestJS is a frontend one, so we are just going to use the models. Not 100% automatic, but still pretty nifty!
Update libs/shared/backend/src/lib/get-robots.function.ts
:
// Libs
import { GiantRobot } from '@giant-robots/shared/api';
[...]
Update apps/backend/src/app/app.service.ts
:
[...]
// Libs
import { GiantRobot } from '@giant-robots/shared/api';
[...]
Update apps/backend/src/app/app.controller.ts
:
[...]
@Controller('v1/giant-robots')
[...]
Go on: launch the frontend and backend apps and they will work just as before; only from now on, whenever a new business requirement about the API layer comes up, you'll just have to update the OpenAPI spec and launch:
nx run shared-api:generate-sources
PermalinkWait a minute, what about backend-netlify
?
Good point. It seems our fantastic "endpoint per environment" system got sidelined by the autogenerated API lib and now we are missing a way to hit the local backend app in development and the Netlify function in production 🤔
No worries! Any OpenAPI generator is aware of the problem and it will provide a solution!
Here's how you do it with the typescript-fetch
generator we are using.
Update apps/frontend/src/app/app.tsx
:
[...]
// Libs
import {
GiantRobotsApi,
GiantRobot,
Configuration,
} from '@giant-robots/shared/api';
[...]
function App() {
const { apiEndPointRobots, fairAdjective } = environment;
const [giantRobots, setGiantRobots] = useState<GiantRobot[]>([]);
useEffect(() => {
const fetchData = async () => {
const apiConfiguration = new Configuration({
basePath: apiEndPointRobots,
});
const api = new GiantRobotsApi(apiConfiguration);
[...]
Update apps/frontend/src/environments/environment.ts
:
[...]
export const environment = {
production: false,
fairAdjective: 'tentative',
apiEndPointRobots: '/api/v1',
};
Update apps/frontend/src/environments/environment.prod.ts
:
export const environment = {
production: true,
fairAdjective: 'annual',
apiEndPointRobots:
'https://laughing-shirley-d85382.netlify.app/.netlify/functions',
};
Almost done! We just need to create a custom webpack config for backend-netlify
to change the name of the bundle from main.js
to giant-robots.js
so that the final URL will be correct on Netlify, too.
Create apps/backend-netlify/webpack.config.js
:
module.exports = (config) => {
return {
...config,
output: { ...config.output, filename: 'giant-robots.js' },
};
};
And update workspace.json
:
[...]
"backend-netlify": {
[...]
"targets": {
"build": {
"executor": "@nrwl/node:build",
"outputs": ["{options.outputPath}"],
"options": {
[...]
"webpackConfig": "apps/backend-netlify/webpack.config.js"
},
[...]
Now let's check if everything still works!
Serve frontend
and backend
and check if the list of giant robots is still getting loaded.
Cool, now push everything you have in order to launch a build on Netlify and check if it's working up there, too.
If you don't know what I'm talking about, you can go back to the previous post in this series and learn how to deploy a Nx app on Netlify in the worst possible way.
PermalinkWrapping up
You just learned how to fix decisions about the API contract into a OpenAPI spec, save it into a Nx lib, and then use that lib as the base autogenerated models and API services.
And did you know how long the list of available generators is? Take a look: openapi-generator.tech/docs/generators
Think: with one of the several Documentation generators you can also have a free user friendly API reference web site, all in the same Nx monorepo! How cool is that?
Next time we are going to talk about tags and how they can help in keeping your growing monorepo clean and organized.
Cover photo by Benyamin Bohlouli on Unsplash