はじめに
swagger-uiの実装をしようと思ったんだけど、前できなかった。
いまならできそう。あと、いろいろキャッチアップする
前提イメージ
- openapiを実装しまする
- aspidaも実装したい
- 新しいアプリを作ってやる。
- monorepoで作ろうか
- turborepo使用
- 構成はopenapiとnext.js
- docker上でturborepoを使ってみるという所業
- だって、localを汚したくない。。
実装
順番はこうかな
- 設計
- 環境構築
- docker
- turborepo
- openapi実装
- next.js実装
- aspidaでopenapiの内容をとってくる
設計
なーーに作ろう。next.jsの強みを調べようか。
...というかそもそも、next.jsってreactのフレームワークだったのかww 知らんかった。
あとnext.jsってSSRなんね。ほえーー。
まぁ、レンダリングの強みを活かしたいから、blogでも作ってみる?chanabe-blog。
ということで簡単に設計
環境構築
じゃあまずは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いれよーー。
$ 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.
おお、なんかできたっぽい。 すげぇファイルいっぱいできてるーー笑
公式の解説を読んでこ。
公式ドキュメントヨミヨミ
これが言っていることをまとめると
- appsの中にアプリケーションをまとめているよ!
- packagesにはuiとtsconfigとeslintのフォルダがあるよ!
- uiはコンポーネントが入っているし、appsからの呼び出しもかんたん!
- tsconfigはいろんなところに共通した設定を共有できるし、独自の設定もできるよ!
- eslintも共通できるし、独自もできちゃう
- コマンド系はturbo.jsonにかこう!
- lint
- lintはrootでコマンド打つだけで全部やってくれるよ!
- しかもcacheを使えば、変更箇所だけやるよ!
- build
- buildもrootでコマンド打つだけで全部やるよb
- これもchache使えば、すぐにできちゃう!
- run
- これでappたちが起動しちゃう!かんたんだね!
- もし全部起動したくなかったら、--filterで絞れるよ!
- lint
んなーーーー、これやってて思った。
Swagger(opanapi)関係ねぇ。
ということは? 構成を変えましょ。
再設計
turborepoの強みとdockerの強みが生きるように。
swaggerの作成
じゃあturborepoのやり方もある程度わかったので、次はswaggerの設定をしよう。
なんか、かんたんにできるっぽい?
やってみる。
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作成にあたっての注意点が乗ってたから読んでみた。
- 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 - HTTPS(TLS暗号化) - 返すステータスはちゃんとしよう - エラー:4xx - 正常:2xx引用元:API設計スキルを次のレベルに引き上げるベストプラクティス22選
あとOpenAPIの作り方についても。
- yamlのオブジェクトは以下の構成 - openapi / info - titleやバージョンなど - バージョンは基本的にセマンティックバージョニング - servers - APIのベースパスを設定できます(/api/v0とか) - それぞれ変数ベースで用意できるし、開発者用・本番用にも分けれられる - components - paths等から呼び出せるコンポーネントを定義する - requestとresponseで表示非表示を制御できるreadOnly/writeOnlyというのがある - security - apiKeyなどを定義できる - 全体に適用したり、個別のpathで設定が可能引用元:作って理解する OpenAPI 3.0 / connexion
あとこれも読んだ、いうてさっきのとあんまり変わらんかったが。
- SSLは使おう - レスポンスは絞れるように - 作成・更新のあとのデータは絞れるように - エラーメッセージはしっかり返そう引用元:翻訳: WebAPI 設計のベストプラクティス
これはAPIのエラーメッセージについて
- エラーメッセージの分類は最小限に。以下くらいで十分。あんまり多くすると開発者の負担になる。 - 200 - 400 - 401 - 402 - 404 - 500 - エラーメッセージは丁寧説明的で。そして開発者に何をすべきかを促すように。引用元:REST API Error Codes 101
これも読んだ。 エラーは簡潔で説明的であれ。
Stoplight Studioでつくっていく
おk。だいぶわかった。
じゃあ作ってみよ。
...だけど作っているときに大変なことがわかった。 StoplightStudioとaspidaがreadOnlyとwriteOnlyをサポートしていないっぽいのだ。まじかよ。
てか色々穴があるな。。ちょっと使いたくないかも。
だけど、一旦これで進めよう。readOnlyとかは別モデルとして定義して上げてやっていこう。。 もしだめっぽかったら、VSCodeの拡張機能で勧めていこう
作った!
docker上で動かしてみる
おー表示された
だけど、なんだかエラーは出ている。
どうやら、Prismだとserverに設定したbaseUrl設定が無効になるらしい。
今回は,わざわざエンドポイントごとに `/api/v0` を入れて書いているが,本来ならば `server:` に一括で指定したい.しかしそうするためには,以下の記事にあるように,現状はPrism以外のツールを使う必要がある模様.引用元:Swagger (OpenAPI) について
うーーん、これを見てみるとどうやら開発者は、「baseURLはあくまでサーバーが管理する必要があるものであり、OpenAPIが管理する必要がない」という主張っぽいな。あくまでpathの管理をするのがPrismaか。
じゃーーー今回はいっか。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アプリを入れてみる。
なお、キャッチアップはこれでやった。
next, react, react-domを入れて、pagesフォルダ作って、yarn devしたらできるというなんともかんたん。
あと、turborepoもcreate-turboみたいな感じの自動setupじゃなくて、manualのsetupをしていこう。
やってみよう。
アプリの枠作成
appsフォルダを作って、その配下にアプリの名前を作る。今回だったら、CDSとCMS。
こんな感じ。
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