ちゃなべの備忘録

ほぼ備忘録です。

openapiとそれ関連いろいろ実装してみるンゴ (前編)【備忘録】

はじめに

swagger-uiの実装をしようと思ったんだけど、前できなかった。
いまならできそう。あと、いろいろキャッチアップする

前提イメージ

  • openapiを実装しまする
  • aspidaも実装したい
  • 新しいアプリを作ってやる。
  • monorepoで作ろうか
    • turborepo使用
  • 構成はopenapiとnext.js
  • docker上でturborepoを使ってみるという所業
    • だって、localを汚したくない。。

実装

順番はこうかな

  1. 設計
  2. 環境構築
    1. docker
    2. turborepo
  3. openapi実装
  4. next.js実装
    1. aspidaでopenapiの内容をとってくる

設計

なーーに作ろう。next.jsの強みを調べようか。 ...というかそもそも、next.jsってreactのフレームワークだったのかww 知らんかった。
あとnext.jsってSSRなんね。ほえーー。

まぁ、レンダリングの強みを活かしたいから、blogでも作ってみる?chanabe-blog。

ということで簡単に設計

サイトマップ
サイトマップ

ERD
ERD

使用サービス相関図
使用サービス相関図

環境構築

じゃあまずはdockerとturborepoで環境を作っていこう。

$ mkdir chanablog
$ cd chanablog
version: '3'

volumes:
  node-modules:


services:
  app:
    build: ./
    volumes:
      - ./src/:/usr/local/src/
      - node-modules:/usr/local/src/node_modules
    working_dir: /usr/local/src
    tty: true
    container_name: chanablog
FROM node:18.15.0-bullseye-slim

Debianの現状の安定バージョンで、slimのnodeを使った。発火!

$ docker compose up -d

じゃあここに、yarnいれて、turborepoいれよーー。

turbo.build

$ npm i yarn
added 1 package in 1s

$ yarn
yarn install v1.22.19
warning package.json: No license field
info No lockfile found.
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
warning No license field
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
Done in 1.01s.

$ ls
node_modules  package-lock.json  package.json  yarn.lock

$ yarn add create-turbo@latest
$ yarn run create-turbo    
yarn run v1.22.19
warning package.json: No license field
$ /usr/local/src/node_modules/.bin/create-turbo

>>> TURBOREPO

>>> Welcome to Turborepo! Let's get you set up with a new codebase.

? Where would you like to create your turborepo? ./app
? Which package manager do you want to use? yarn

>>> Created a new turborepo with the following:

 - apps/web: Next.js with TypeScript
 - apps/docs: Next.js with TypeScript
 - packages/ui: Shared React component library
 - packages/eslint-config-custom: Shared configuration (ESLint)
 - packages/tsconfig: Shared TypeScript `tsconfig.json`

>>> Success! Created a new Turborepo at "app".
Inside that directory, you can run several commands:

  yarn run build
     Build all apps and packages

  yarn run dev
     Develop all apps and packages

Turborepo will cache locally by default. For an additional
speed boost, enable Remote Caching with Vercel by
entering the following command:

  npx turbo login

We suggest that you begin by typing:

  cd app
  npx turbo login

Done in 178.97s.

おお、なんかできたっぽい。 すげぇファイルいっぱいできてるーー笑

公式の解説を読んでこ。

公式ドキュメントヨミヨミ

turbo.build

これが言っていることをまとめると

  • appsの中にアプリケーションをまとめているよ!
  • packagesにはuiとtsconfigとeslintのフォルダがあるよ!
    • uiはコンポーネントが入っているし、appsからの呼び出しもかんたん!
    • tsconfigはいろんなところに共通した設定を共有できるし、独自の設定もできるよ!
    • eslintも共通できるし、独自もできちゃう
  • コマンド系はturbo.jsonにかこう!
    • lint
      • lintはrootでコマンド打つだけで全部やってくれるよ!
      • しかもcacheを使えば、変更箇所だけやるよ!
    • build
      • buildもrootでコマンド打つだけで全部やるよb
      • これもchache使えば、すぐにできちゃう!
    • run
      • これでappたちが起動しちゃう!かんたんだね!
      • もし全部起動したくなかったら、--filterで絞れるよ!

んなーーーー、これやってて思った。

Swagger(opanapi)関係ねぇ。

ということは? 構成を変えましょ。

再設計

turborepoの強みとdockerの強みが生きるように。

swaggerの作成

じゃあturborepoのやり方もある程度わかったので、次はswaggerの設定をしよう。

なんか、かんたんにできるっぽい?

やってみる。

c-a-p-engineer.github.io

chanablog/
├── services/
│   ├── api/
│   │   └── openapi.yaml
│   └── client/
│       ├── src/
│       └── Dockerfile
└── docker-compose.yml
version: '3'

volumes:
  node-modules:


services:
  client:
    build: ./services/client/
    volumes:
      - ./services/client/src/:/usr/local/src/
      - node-modules:/usr/local/src/node_modules
    working_dir: /usr/local/src
    tty: true
    container_name: chanablog-client
    ports:
      - 3000:3000
      - 3001:3001

  swagger-editor:
    image: swaggerapi/swagger-editor
    container_name: "chanablog-swagger-editor"
    ports:
      - "8001:8080"

  swagger-ui:
    image: swaggerapi/swagger-ui
    container_name: "chanablog-swagger-ui"
    ports:
      - "8002:8080"
    volumes:
      - ./services/api/openapi.yml:/openapi.yml
    environment:
      SWAGGER_JSON: /openapi.yml

  swagger-api:
    image: stoplight/prism:latest
    container_name: "chanablog-swagger-api"
    ports:
      - "8003:4010"
    command: mock -h 0.0.0.0 /openapi.yml
    volumes:
      - ./services/api/openapi.yml:/openapi.yml
openapi: "3.0.3"

info:
  title: "Test1-API"
  version: "1.0.0"

paths:
  "/helloWorld":
    get:
      responses:
        "200":
          description: "test-ok"
          content:
            application/json:
              schema:
                type: string
                example: "Hello World"

じゃあ起動して見てみる。

おぉーーー!これだけでこんなに見れるのか!

だけど、Editorこれいらんな。
Stoplight Studioがlocalにあるからそれを使おう。

じゃあ編集してみる。

作成にあたって

API作成にあたっての注意点が乗ってたから読んでみた。

qiita.com

- URLはケバブケース - クエリ、パラメータはキャメルケース - 例) http://api.domain.com/users/{userId} - jsonのプロパティにもキャメルケース - 例) data: {userName: "Mohammad Faisal", userId: "1"} - APIのURLの前にversionを書く - 例) http://api.domain.com/v1/shops/3/products - 以下のURLは入れておく - /health - 必須 - `200 OK`ステータスコードで応答します。 - /version - 必須 - バージョン番号で応答します。 - /metrics - 必須 - 平均応答時間などのさまざまなmetricsを提供します。 - /debug - システムの動作に関する情報や、エラーが発生した場合のスタックトレースなどの情報を提供します。 - /status - サーバーの稼働時間、リクエスト数、レスポンス時間などの情報を提供します。 - リソースの総数をレスポンスに含める - 例) {total: 34, users: [ ... ]} - limitパラメータとoffsetパラメータを受け入れる - 例) GET /shops?offset=5&limit=5 - セキュリティ - CORS - HTTPSTLS暗号化) - 返すステータスはちゃんとしよう - エラー:4xx - 正常:2xx
引用元:API設計スキルを次のレベルに引き上げるベストプラクティス22選

あとOpenAPIの作り方についても。

qiita.com

- yamlのオブジェクトは以下の構成 - openapi / info - titleやバージョンなど - バージョンは基本的にセマンティックバージョニング - servers - APIのベースパスを設定できます(/api/v0とか) - それぞれ変数ベースで用意できるし、開発者用・本番用にも分けれられる - components - paths等から呼び出せるコンポーネントを定義する - requestとresponseで表示非表示を制御できるreadOnly/writeOnlyというのがある - security - apiKeyなどを定義できる - 全体に適用したり、個別のpathで設定が可能
引用元:作って理解する OpenAPI 3.0 / connexion

あとこれも読んだ、いうてさっきのとあんまり変わらんかったが。

qiita.com

- SSLは使おう - レスポンスは絞れるように - 作成・更新のあとのデータは絞れるように - エラーメッセージはしっかり返そう
引用元:翻訳: WebAPI 設計のベストプラクティス

これはAPIのエラーメッセージについて

blog.restcase.com

- エラーメッセージの分類は最小限に。以下くらいで十分。あんまり多くすると開発者の負担になる。 - 200 - 400 - 401 - 402 - 404 - 500 - エラーメッセージは丁寧説明的で。そして開発者に何をすべきかを促すように。
引用元:REST API Error Codes 101

これも読んだ。 エラーは簡潔で説明的であれ。

nordicapis.com

Stoplight Studioでつくっていく

おk。だいぶわかった。
じゃあ作ってみよ。

...だけど作っているときに大変なことがわかった。 StoplightStudioとaspidaがreadOnlyとwriteOnlyをサポートしていないっぽいのだ。まじかよ。

てか色々穴があるな。。ちょっと使いたくないかも。

だけど、一旦これで進めよう。readOnlyとかは別モデルとして定義して上げてやっていこう。。 もしだめっぽかったら、VSCode拡張機能で勧めていこう

zenn.dev

作った!

docker上で動かしてみる

おー表示された

だけど、なんだかエラーは出ている。

どうやら、Prismだとserverに設定したbaseUrl設定が無効になるらしい。

今回は,わざわざエンドポイントごとに `/api/v0` を入れて書いているが,本来ならば `server:` に一括で指定したい.しかしそうするためには,以下の記事にあるように,現状はPrism以外のツールを使う必要がある模様.
引用元:Swagger (OpenAPI) について

うーーん、これを見てみるとどうやら開発者は、「baseURLはあくまでサーバーが管理する必要があるものであり、OpenAPIが管理する必要がない」という主張っぽいな。あくまでpathの管理をするのがPrismaか。

github.com

じゃーーー今回はいっか。serverから/api/v1の部分を消す。

# 変更前
servers:
  - url: 'http://{host}:{port}/api/{version}'
    description: development
    variables:
      host:
        default: localhost
      port:
        default: '8002'
      version:
        default: v1

# 変更後
servers:
  - url: 'http://{host}:{port}/v1'
    description: development
    variables:
      host:
        default: localhost
      port:
        default: '8002'

よし、動いた。

turborepoの導入

次は、nextアプリを入れてみる。

なお、キャッチアップはこれでやった。

ayumu1212.hatenablog.com

next, react, react-domを入れて、pagesフォルダ作って、yarn devしたらできるというなんともかんたん。

あと、turborepoもcreate-turboみたいな感じの自動setupじゃなくて、manualのsetupをしていこう。

やってみよう。

アプリの枠作成

appsフォルダを作って、その配下にアプリの名前を作る。今回だったら、CDSCMS

こんな感じ。

src/
├── apps/
│   ├── cds/
│   └── cms/
├── node_modules/
├── packages.json
└── yarn.lock

そして、cds配下で必要なpackageをinstallする。

$ cd apps/cds/

# package.jsonの空fileを作り、yarn addしたときに、ここに入れるようにする。
$ touch package.json

$ echo "{}" > package.json

$ yarn add next react react-dom

# next.jsアプリの準備
$ mkdir pages
$ touch pages/index.tsx
$ vi pages/index.tsx
export default function View() {
  return <div>this is cds</div>
}

apps/cds/package.jsonを編集する。

{
  "name": "cds",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev --port 3001",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "^13.3.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

じゃあ、アプリを起動してみる。

 yarn dev
yarn run v1.22.19
warning ../../package.json: No license field
$ next dev --port 3001
ready - started server on 0.0.0.0:3001, url: http://localhost:3001
It looks like you're trying to use TypeScript but do not have the required package(s) installed.
Installing dependencies

If you are not trying to use TypeScript, please remove the tsconfig.json file from your package root (and any TypeScript files in your pages directory).


Installing devDependencies (yarn):
- typescript
- @types/react
- @types/node

warning ../../package.json: No license field
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 6 new dependencies.
info Direct dependencies
├─ @types/node@18.15.11
├─ @types/react@18.0.33
└─ typescript@5.0.4
info All dependencies
├─ @types/node@18.15.11
├─ @types/prop-types@15.7.5
├─ @types/react@18.0.33
├─ @types/scheduler@0.16.3
├─ csstype@3.1.2
└─ typescript@5.0.4

We detected TypeScript in your project and created a tsconfig.json file for you.

Attention: Next.js now collects completely anonymous telemetry regarding usage.
This information is used to shape Next.js' roadmap and prioritize features.
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
https://nextjs.org/telemetry

warning ../../package.json: No license field
event - compiled client and server successfully in 2.6s (154 modules)
wait  - compiling / (client and server)...
event - compiled client and server successfully in 341 ms (166 modules)
wait  - compiling /_error (client and server)...
event - compiled client and server successfully in 210 ms (167 modules)

完璧。ちゃんと表示もされている。

nextで揃えられた現状のフォルダ構成はこんな感じ。

src/
├── apps/
│   ├── cds/
│   │   ├── .next/
│   │   ├── node_modules/
│   │   ├── pages/
│   │   │   └── index.tsx
│   │   ├── next-env.d.ts
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── yarn.lock
│   └── cms/
├── node_modules/
├── packages.json
└── yarn.lock
turbo導入

rootでturboをinstall

$ yarn add turbo

次にturbo.jsonを定義した。

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": [
        "^build"
      ],
      "outputs": [
        ".next/**",
        "!.next/cache/**"
      ]
    },
    "lint": {},
    "dev": {
      "cache": false
    }
  }
}

topのpackage.jsonも編集した。

{
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint"
  },
  "dependencies": {
    "turbo": "^1.8.8"
  }
}

じゃあ実行。

$ yarn dev    
yarn run v1.22.19
$ turbo run dev
• Running dev
• Remote caching disabled
root task dev (turbo run dev) looks like it invokes turbo and might cause a loop

No tasks were executed as part of this run.

 Tasks:    0 successful, 0 total
Cached:    0 cached, 0 total
  Time:    2.188s 

 ERROR  run failed: command  exited (1)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

$ yarn build
yarn run v1.22.19
$ turbo run build
 ERROR  run failed: error preparing engine: Could not find "___ROOT___#build" in root turbo.json
Turbo error: error preparing engine: Could not find "___ROOT___#build" in root turbo.json
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

ん?実行もbuildもエラーがでた。

調べてもあんまりエラーでないし、なんやろ。

とういことで、別dockerコンテナでcreate-turboして、ソースコードを見比べてみた。 そしたら、package.jsonにあるworkspacesというプロパティがないことに気がついた。

ということでいれる。

{
  "private": true,
  "workspaces": [
    "apps/cds"
  ],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint"
  },
  "dependencies": {
    "turbo": "^1.8.8"
  }
}

これで、yarn devしたら動いた。

そっかーー、workspaceとして登録しないと認識しないのね。

package配置

次にpackageを配置していく。 中身はui, tsconfig, eslint-config-customにする。

そして、それぞれをコマンドで作成する。

$  npm init -w packages/ui
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (ui) @ui
Sorry, name can only contain URL-friendly characters.
package name: (ui) ui
version: (1.0.0) 
description: package of ui components
entry point: (index.js) index.tsx
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /usr/local/src/packages/ui/package.json:

{
  "name": "ui",
  "version": "1.0.0",
  "description": "package of ui components",
  "main": "index.tsx",
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) 

added 2 packages in 1m

3 packages are looking for funding
  run `npm fund` for details

$  npm init -w packages/tsconfig

$  npm init -w packages/eslint-config-custom

packages内に諸々のfileができた。それぞれ構築していく。

ちなみに、package.jsonのworkspacesはワイルドカードにしておこう。

{

  "workspaces": [
    "apps/*",
    "packages/*"
  ],

}
package/tsconfigの設定

これはcreate-turboを参考にした。
フォルダ構成は以下のように。

packages/
└── tsconfig/
    ├── base.json     # 基本となるようなtsconfig
    ├── nextjs.json     # nextjs特有のtsconfig
    └── package.json
{
  "name": "tsconfig",
  "version": "1.0.0",
  "private": true,
  "description": "package of tsconfig files",
  "files": [
    "base.json",
    "nextjs.json",
    "react-library.json"
  ]
}

そして、この設定をアプリの方で読み込んであげる。

{
  "extends": "tsconfig/nextjs.json",
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

これで完了。

package/eslint-config-customの設定

lintの設定をしていく。

これも参考はcreate-turbo

まずは、コマンドでpackageをinstallしていく。

$ yarn workspace eslint-config-custom add eslint eslint-config-next eslint-config-prettier eslint-plugin-react eslint-config-turbo

$ yarn workspace eslint-config-custom add --dev typescript

そして、package.jsonとindex.jsを修正する。

{
  "name": "eslint-config-custom",
  "version": "1.0.0",
  "private": true,
  "description": "base of eslint",
  "main": "index.js",
  "dependencies": {
    "eslint": "^8.38.0",
    "eslint-config-next": "^13.3.0",
    "eslint-config-prettier": "^8.8.0",
    "eslint-config-turbo": "^1.8.8",
    "eslint-plugin-react": "^7.32.2"
  },
  "devDependencies": {
    "typescript": "^5.0.4"
  }
}
module.exports = {
  extends: ["next", "turbo", "prettier"],
  rules: {
    "@next/next/no-html-link-for-pages": "off",
  },
  parserOptions: {
    babelOptions: {
      presets: [require.resolve("next/babel")],
    },
  },
};

ここまでできたら、アプリの方を修正する。
というか、eslintいれてなかったよ。

$ yarn workspace cds add --dev eslint

そして、eslintの設定をつくるぞーー

module.exports = {
  root: true,
  extends: ["custom"],
};

これでlintを回してみる!

$ yarn lint
yarn run v1.22.19
$ turbo run lint
• Packages in scope: cds, eslint-config-custom, tsconfig, ui
• Running lint in 4 packages
• Remote caching disabled
cds:lint: cache miss, executing ed4501e77bc1f2a7
cds:lint: $ next lint
cds:lint: ✔ No ESLint warnings or errors

 Tasks:    1 successful, 1 total
Cached:    0 cached, 1 total
  Time:    2.146s

Done in 2.25s.

いいね、回った。

package/uiの設定

これもturborepoを参考にした。 フォルダ構成は以下のように。

$ yarn workspace ui add --dev typescript react @types/react

これで必要なコンポーネント追加した。

{
  "name": "ui",
  "version": "1.0.0",
  "description": "package of ui components",
  "main": "index.tsx",
  "devDependencies": {
    "@types/react": "^18.0.35",
    "react": "^18.2.0",
    "typescript": "^5.0.4"
  }
}

次にtsconfigをuiに反映させる。packages/tsconfigにreact-library.jsonを追加。

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "React Library",
  "extends": "./base.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "lib": [
      "ES2015"
    ],
    "module": "ESNext",
    "target": "es6"
  }
}
{
  "name": "tsconfig",
  "version": "1.0.0",
  "private": true,
  "description": "package of tsconfig files",
  "files": [
    "base.json",
    "nextjs.json",
    "react-library.json" # 追加
  ]
}

uiに反映させるために、tsconfigファイルを追加。

{
  "extends": "tsconfig/react-library.json",
  "include": ["."],
  "exclude": ["dist", "build", "node_modules"]
}

turboを動かしてみる。動いたー!

これで準備はできたので、さっさとUIを作ろう。

後編に続く

引用サイト

結局ReactとNext.jsのどちらで開発を進めればいいの? - Qiita

Getting Started with Turborepo – Turborepo

API設計スキルを次のレベルに引き上げるベストプラクティス22選 - Qiita