.../articles/
A Richer Client Life Protected by Generated Types, with OpenAPI Generator and TypeScript

A Richer Client Life Protected by Generated Types, with OpenAPI Generator and TypeScript

2020.02.28

Table of Contents

  1. Benefits of writing documentation with OpenAPI, etc.
  2. Generating client code with OpenAPI Generator
  3. Using it with Vue and Nuxt
  4. Easy to keep up with API changes, and type-safe too — a happy result

Benefits of writing documentation with OpenAPI, etc.

As mentioned in the blog posts below, we make use of OpenAPI at our company to document API specifications.

When the frontend and backend are implemented by different people, or when there's a chance that different people will implement them in the future, or simply from the standpoint of building documentation that's easy to read, we feel there are significant benefits to using OpenAPI. And what we feel amplifies those benefits even further is development using OpenAPI Generator. At our company, we often use Nuxt.js + TypeScript for the frontend, and when we use the types output by OpenAPI Generator in the parts that make API requests with axios and the like, we get benefits such as:

  • No need to write type-related files ourselves
  • Fewer mistakes since we're not writing them by hand (though of course this doesn't help if the original file itself is wrong)
  • Easier to keep up with API changes

In this post, I'd like to introduce how to set up OpenAPI Generator for Vue.js and Nuxt.js.


1. Generating client code with OpenAPI Generator

1.1 Installing the package

There's also a way to install it globally with yarn or brew, but since I also want to use it in CI checks, I install openapi-generator per-project with yarn.

$ yarn add -D @openapitools/openapi-generator-cli

or

$ npm install @openapitools/openapi-generator-cli -D

1.2 Adding to package.json scripts

Add a script such as generate-client to generate the client.

openapi-generator generate -g typescript-axios

-g selects which generator to use. Here we're using typescript-axios, but you can change it depending on the language/framework you use (see reference *2).

I've also included check-yml, format-yml, and validate-schema, but I'd recommend these since they're handy for CI checks — using prettier to check the yaml formatting, and validating with the generator 😎 { "name": "Application-Name", "version": "1.0.0", "private": true, "scripts": { "check-yml": "prettier --check './path/to/openapi.yaml'", "format-yml": "prettier --write './path/to/openapi.yaml'", "validate-schema": "openapi-generator validate -i ./path/to/openapi.yaml", "generate-client": "openapi-generator generate -g typescript-axios -i ./path/to/openapi.yaml -o ./frontoend-application-directory/src/types/typescript-axios", }, "devDependencies": { "@openapitools/openapi-generator-cli": "^1.0.10-4.2.3", } } 1.3 Generating the client Generate the client using the script we just registered. $ yarn generate-client The generated source looks like this. At this point, client generation is complete and we're ready to go. $ tree -L 2 ./types ./types ├── client-axios │ ├── api.ts │ ├── base.ts │ ├── configuration.ts │ ├── git_push.sh │ └── index.ts └── index.d.ts 2. Using it with Vue and Nuxt From here, let's talk about how we use the client generated above. I'll explain how we use the generated type information, based on the source of a login screen. 2.1 The Yaml and the generated client Below is the loginId and password information required for login, included in api.ts. When you generate from the yaml file that contains the original OpenAPI definitions, the contents of api.ts are produced. Excerpt of the LoginRequest section from openapi.yaml. LoginRequest: title: LoginRequest type: object description: 認証情報 required: [loginId, password] properties: loginId: type: string description: ログインID password: type: string description: パスワード File: ./types/typescript-client/api.ts /** * 認証情報 * @export * @interface LoginRequest */ export interface LoginRequest { /** * ログインID * @type {string} * @memberof Auth */ loginId: string; /** * パスワード * @type {string} * @memberof Auth */ password: string; } 2.2 Using the generated client from Vue Let's load the client information generated above into login.vue. We use the generated type information in the interface for data. Normally you'd need to write this information yourself, but here we just make use of what was generated, which saves a lot of effort 🤗 File: ./pages/login.vue import Vue from 'vue' import Cookies from 'universal-cookie' import { LoginRequest } from '@/types/typescript-axios' interface Data { value: LoginRequest, } export default Vue.extend({ components: { }, data: (): Data => { return { value: { loginId: '', password: '', }, } }, methods: { async login() { try { await this.$store.dispatch('auth/login', { basePath: process.env.VUE_APP_BASE_PATH, // API URL value: this.value, cookie: new Cookies(), stage: process.env.VUE_APP_STAGE, // just used to avoid attaching the Secure attribute to cookies locally (feel free to remove if you don't need it) }) } catch (error) { // handle the error appropriately } }, }, }) 2.3 Using the generated client from the store (Vuex) Here's the content of store/auth, which is called from the login method in login.vue. Here, in addition to the LoginRequest type information, we also import the AuthApi class. We use this within the store to make requests to the API. Since the generated AuthApi already contains the code that uses axios to make the request, the store just needs to call the method corresponding to the endpoint (a nice convenience point!). AuthApi({ basePath: params.basePath }) Here we pass the API URL, but for endpoints that require authentication, if you pass a token like below, it will be added to the header according to the specified authentication method (e.g. Bearer) at request time. new MeApi({ basePath: params.basePath, accessToken: params.accessToken, }) File: ./store/auth.ts import { Module, ActionTree, GetterTree, MutationTree } from 'vuex' import { LoginRequest, AuthApi } from '@/types/typescript-axios' import Cookies, { CookieSetOptions } from 'universal-cookie' import { RootState } from '@/types/index' interface AuthState { } interface LoginParams { basePath?: string, value: LoginRequest, cookie: Cookies, stage: string, } export const state = (): AuthState => ({ }) export const getters: GetterTree<AuthState, RootState> = { } export const mutations: MutationTree<AuthState> = { } export const actions: ActionTree<AuthState, RootState> = { async login(_ctx, params: LoginParams) { try { const { data } = await(new AuthApi({ basePath: params.basePath })) .postAuthLogin(params.value) params.cookie.set( 'accessToken', data.accessToken, { secure: params.stage !== 'local', maxAge: data.expiresIn, } as CookieSetOptions, ) } catch (error) { throw error.response } }, } export const auth: Module<AuthState, RootState> = { namespaced: true, getters, mutations, actions, } 3. Easy to keep up with API changes, and type-safe too — a happy result Above, we implemented a flow for generating type information from OpenAPI and using it on the frontend. Keeping track of and following API changes can be a fairly painstaking part of frontend integration, but by using the Generator, build errors point out exactly where fixes are needed, which helps prevent mistakes. Going forward, I'd like to incorporate this well into frontend testing, but since that part isn't done yet, I'll leave it as a future task. References *1 https://openapi-generator.tech/ *2 Trying out OpenAPI generator *3 https://github.com/OpenAPITools/openapi-generator

written by

.../article/

Articles

All articles

From Firebase to Vercel, Contentful to microCMS — a migration log written with Claude Code

From Firebase to Vercel, Contentful to microCMS — a migration log written with Claude Code

We moved our corporate site's hosting and CMS, and made it bilingual along the way. The constraints we only found by running against real data were more useful than the migration itself, so this post focuses on where we got stuck.

Can't Read POST Data with Firebase Functions × Remix?

Can't Read POST Data with Firebase Functions × Remix?

How to read POST data from a Remix action when running on Firebase Functions.

Generative AI for Executives and Leaders: An Approach to Self-Driven DX

Generative AI for Executives and Leaders: An Approach to Self-Driven DX

Building a structure where executives and leaders themselves can identify issues and evaluate solutions using generative AI. We introduce how combining this with our hands-on support dramatically improves both the quality and speed of digital transformation.

Deploying a Monorepo Next.js App (App Router) to AWS Amplify

Deploying a Monorepo Next.js App (App Router) to AWS Amplify

Notes on the obstacles we hit while deploying a Next.js app managed in a monorepo to AWS Amplify.

Keeping Production Running Smoothly with Remote Work and Online Meetings [Documentation]

Keeping Production Running Smoothly with Remote Work and Online Meetings [Documentation]

Many production companies have adopted remote work as a result of the pandemic, and we are one of them.

Designing an E-Commerce Site That Sells: How to Find Great Reference Examples

Designing an E-Commerce Site That Sells: How to Find Great Reference Examples

There is no single formula for e-commerce design that sells. Driving revenue requires a solid concept, and getting to that concept requires thorough research.

Productivity Tools We Recommend as a Production Company, Including Services That Work Well Solo

Productivity Tools We Recommend as a Production Company, Including Services That Work Well Solo

With remote work becoming the norm during the COVID-19 pandemic, our team now works from home most days of the week.

We Released Thought Recorder, a Figma Plugin for Keeping a Commit History of Your Designs

We Released Thought Recorder, a Figma Plugin for Keeping a Commit History of Your Designs

We hope this helps web designers who work in Figma. Read on for how to use it.

How to Build an E-Commerce Site, and Which Platforms We Recommend

How to Build an E-Commerce Site, and Which Platforms We Recommend

Shopping online for fashion, appliances, and even groceries is now routine. With the pandemic accelerating the shift, we receive a steady stream of questions about which platform to use and how much it costs.

Generating FastAPI Schema Classes from OpenAPI

Generating FastAPI Schema Classes from OpenAPI

We chose FastAPI, a relatively modern framework, for a Python API project. FastAPI can generate an OpenAPI definition from your backend code, but here we do the opposite: generating FastAPI schema classes from an OpenAPI definition prepared in advance.

View all articles

Contact us