[{"data":1,"prerenderedAt":36058},["ShallowReactive",2],{"blog-posts":3},[4,968,2912,5995,6087,6405,6779,7061,7715,8506,9608,12805,14608,18874,20433,21399,21934,22298,23667,26498,28649,29622,31605,34852],{"id":5,"title":6,"body":7,"date":960,"description":961,"extension":962,"meta":963,"navigation":146,"path":964,"seo":965,"stem":966,"__hash__":967},"blog\u002Fblog\u002Fnuxt-content-renewal.md","個人ブログを Nuxt Content でリニューアルした話",{"type":8,"value":9,"toc":947},"minimark",[10,14,18,21,24,30,52,57,65,70,78,82,86,97,102,251,256,382,385,388,398,463,466,469,476,483,636,640,643,650,703,710,751,755,769,820,823,826,829,836,917,920,923,940,943],[11,12,13],"h2",{"id":13},"はじめに",[15,16,17],"p",{},"これまで Astro 5 で構築していた個人ブログを、Nuxt Content v3 を中心とした構成にリニューアルしました。",[15,19,20],{},"Astro は素晴らしいフレームワークですが、Vue エコシステムが好きな自分にとって、Vue コンポーネントを自然に書ける環境に移行したいと思っていました。",[11,22,23],{"id":23},"新しい技術スタック",[15,25,26],{},[27,28,29],"strong",{},"コア",[31,32,33,40,46],"ul",{},[34,35,36,39],"li",{},[27,37,38],{},"Nuxt 4"," - Vue フルスタックフレームワーク",[34,41,42,45],{},[27,43,44],{},"Nuxt Content v3"," - SQLite ベースのコンテンツ管理",[34,47,48,51],{},[27,49,50],{},"Vite+"," - Vite \u002F Rolldown \u002F Vitest \u002F Oxlint を統合したツールチェーン",[15,53,54],{},[27,55,56],{},"スタイリング",[31,58,59],{},[34,60,61,64],{},[27,62,63],{},"UnoCSS"," - Tailwind 互換のオンデマンド CSS エンジン",[15,66,67],{},[27,68,69],{},"デプロイ",[31,71,72],{},[34,73,74,77],{},[27,75,76],{},"Cloudflare Workers"," - エッジでの SSR \u002F 静的配信",[11,79,81],{"id":80},"astro-から-nuxt-content-v3-への移行","Astro から Nuxt Content v3 への移行",[83,84,85],"h3",{"id":85},"コンテンツコレクションの違い",[15,87,88,89,93,94,96],{},"Astro の ",[90,91,92],"code",{},"defineCollection"," と Nuxt Content v3 の ",[90,95,92],{}," は似ていますが、スキーマ定義と取得 API が異なります。",[15,98,99],{},[27,100,101],{},"Astro の場合",[103,104,109],"pre",{"className":105,"code":106,"language":107,"meta":108,"style":108},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F src\u002Fcontent\u002Fconfig.ts\nimport { defineCollection, z } from 'astro:content';\n\nconst blogCollection = defineCollection({\n  type: 'content',\n  schema: z.object({\n    title: z.string(),\n    date: z.string().transform((str) => new Date(str)),\n  }),\n});\n","typescript","",[90,110,111,120,141,148,168,180,191,203,239,245],{"__ignoreMap":108},[112,113,116],"span",{"class":114,"line":115},"line",1,[112,117,119],{"class":118},"sJ8bj","\u002F\u002F src\u002Fcontent\u002Fconfig.ts\n",[112,121,123,127,131,134,138],{"class":114,"line":122},2,[112,124,126],{"class":125},"szBVR","import",[112,128,130],{"class":129},"sVt8B"," { defineCollection, z } ",[112,132,133],{"class":125},"from",[112,135,137],{"class":136},"sZZnC"," 'astro:content'",[112,139,140],{"class":129},";\n",[112,142,144],{"class":114,"line":143},3,[112,145,147],{"emptyLinePlaceholder":146},true,"\n",[112,149,151,154,158,161,165],{"class":114,"line":150},4,[112,152,153],{"class":125},"const",[112,155,157],{"class":156},"sj4cs"," blogCollection",[112,159,160],{"class":125}," =",[112,162,164],{"class":163},"sScJk"," defineCollection",[112,166,167],{"class":129},"({\n",[112,169,171,174,177],{"class":114,"line":170},5,[112,172,173],{"class":129},"  type: ",[112,175,176],{"class":136},"'content'",[112,178,179],{"class":129},",\n",[112,181,183,186,189],{"class":114,"line":182},6,[112,184,185],{"class":129},"  schema: z.",[112,187,188],{"class":163},"object",[112,190,167],{"class":129},[112,192,194,197,200],{"class":114,"line":193},7,[112,195,196],{"class":129},"    title: z.",[112,198,199],{"class":163},"string",[112,201,202],{"class":129},"(),\n",[112,204,206,209,211,214,217,220,224,227,230,233,236],{"class":114,"line":205},8,[112,207,208],{"class":129},"    date: z.",[112,210,199],{"class":163},[112,212,213],{"class":129},"().",[112,215,216],{"class":163},"transform",[112,218,219],{"class":129},"((",[112,221,223],{"class":222},"s4XuR","str",[112,225,226],{"class":129},") ",[112,228,229],{"class":125},"=>",[112,231,232],{"class":125}," new",[112,234,235],{"class":163}," Date",[112,237,238],{"class":129},"(str)),\n",[112,240,242],{"class":114,"line":241},9,[112,243,244],{"class":129},"  }),\n",[112,246,248],{"class":114,"line":247},10,[112,249,250],{"class":129},"});\n",[15,252,253],{},[27,254,255],{},"Nuxt Content v3 の場合",[103,257,259],{"className":105,"code":258,"language":107,"meta":108,"style":108},"\u002F\u002F content.config.ts\nimport { defineContentConfig, defineCollection, z } from '@nuxt\u002Fcontent';\n\nexport default defineContentConfig({\n  collections: {\n    blog: defineCollection({\n      type: 'page',\n      source: 'blog\u002F**',\n      schema: z.object({\n        title: z.string(),\n        date: z.string(),\n      }),\n    }),\n  },\n});\n",[90,260,261,266,280,284,297,302,311,321,331,340,349,359,365,371,377],{"__ignoreMap":108},[112,262,263],{"class":114,"line":115},[112,264,265],{"class":118},"\u002F\u002F content.config.ts\n",[112,267,268,270,273,275,278],{"class":114,"line":122},[112,269,126],{"class":125},[112,271,272],{"class":129}," { defineContentConfig, defineCollection, z } ",[112,274,133],{"class":125},[112,276,277],{"class":136}," '@nuxt\u002Fcontent'",[112,279,140],{"class":129},[112,281,282],{"class":114,"line":143},[112,283,147],{"emptyLinePlaceholder":146},[112,285,286,289,292,295],{"class":114,"line":150},[112,287,288],{"class":125},"export",[112,290,291],{"class":125}," default",[112,293,294],{"class":163}," defineContentConfig",[112,296,167],{"class":129},[112,298,299],{"class":114,"line":170},[112,300,301],{"class":129},"  collections: {\n",[112,303,304,307,309],{"class":114,"line":182},[112,305,306],{"class":129},"    blog: ",[112,308,92],{"class":163},[112,310,167],{"class":129},[112,312,313,316,319],{"class":114,"line":193},[112,314,315],{"class":129},"      type: ",[112,317,318],{"class":136},"'page'",[112,320,179],{"class":129},[112,322,323,326,329],{"class":114,"line":205},[112,324,325],{"class":129},"      source: ",[112,327,328],{"class":136},"'blog\u002F**'",[112,330,179],{"class":129},[112,332,333,336,338],{"class":114,"line":241},[112,334,335],{"class":129},"      schema: z.",[112,337,188],{"class":163},[112,339,167],{"class":129},[112,341,342,345,347],{"class":114,"line":247},[112,343,344],{"class":129},"        title: z.",[112,346,199],{"class":163},[112,348,202],{"class":129},[112,350,352,355,357],{"class":114,"line":351},11,[112,353,354],{"class":129},"        date: z.",[112,356,199],{"class":163},[112,358,202],{"class":129},[112,360,362],{"class":114,"line":361},12,[112,363,364],{"class":129},"      }),\n",[112,366,368],{"class":114,"line":367},13,[112,369,370],{"class":129},"    }),\n",[112,372,374],{"class":114,"line":373},14,[112,375,376],{"class":129},"  },\n",[112,378,380],{"class":114,"line":379},15,[112,381,250],{"class":129},[15,383,384],{},"Nuxt Content v3 はコレクションを SQLite に保存します。ビルド時にコンテンツを処理してデータベースを構築し、クエリを高速化しています。",[83,386,387],{"id":387},"コンテンツの取得",[15,389,88,390,393,394,397],{},[90,391,392],{},"getCollection"," に対応するのが、Nuxt Content v3 の ",[90,395,396],{},"queryCollection"," です。",[103,399,401],{"className":105,"code":400,"language":107,"meta":108,"style":108},"\u002F\u002F Nuxt Content v3\nconst posts = await queryCollection('blog')\n  .order('date', 'DESC')\n  .all();\n",[90,402,403,408,432,453],{"__ignoreMap":108},[112,404,405],{"class":114,"line":115},[112,406,407],{"class":118},"\u002F\u002F Nuxt Content v3\n",[112,409,410,412,415,417,420,423,426,429],{"class":114,"line":122},[112,411,153],{"class":125},[112,413,414],{"class":156}," posts",[112,416,160],{"class":125},[112,418,419],{"class":125}," await",[112,421,422],{"class":163}," queryCollection",[112,424,425],{"class":129},"(",[112,427,428],{"class":136},"'blog'",[112,430,431],{"class":129},")\n",[112,433,434,437,440,442,445,448,451],{"class":114,"line":143},[112,435,436],{"class":129},"  .",[112,438,439],{"class":163},"order",[112,441,425],{"class":129},[112,443,444],{"class":136},"'date'",[112,446,447],{"class":129},", ",[112,449,450],{"class":136},"'DESC'",[112,452,431],{"class":129},[112,454,455,457,460],{"class":114,"line":150},[112,456,436],{"class":129},[112,458,459],{"class":163},"all",[112,461,462],{"class":129},"();\n",[15,464,465],{},"Vue ページ内では auto-import されるため、import 文が不要です。",[83,467,468],{"id":468},"ルーティング",[15,470,471,472,475],{},"Astro のファイルベースルーティングと同様に、Nuxt も ",[90,473,474],{},"pages\u002F"," ディレクトリでルーティングを管理します。",[15,477,478,479,482],{},"ブログ記事の詳細ページは ",[90,480,481],{},"pages\u002Fposts\u002F[...slug].vue"," として定義し、パスからスラッグを取得してコンテンツをクエリしています。",[103,484,486],{"className":105,"code":485,"language":107,"meta":108,"style":108},"\u002F\u002F app\u002Fpages\u002Fposts\u002F[...slug].vue\nconst route = useRoute();\nconst slug = Array.isArray(route.params.slug)\n  ? route.params.slug.join('\u002F')\n  : route.params.slug;\n\nconst { data: post } = await useAsyncData(`post-${slug}`, () =>\n  queryCollection('blog').path(`\u002Fblog\u002F${slug}`).first()\n);\n",[90,487,488,493,507,525,543,551,555,599,631],{"__ignoreMap":108},[112,489,490],{"class":114,"line":115},[112,491,492],{"class":118},"\u002F\u002F app\u002Fpages\u002Fposts\u002F[...slug].vue\n",[112,494,495,497,500,502,505],{"class":114,"line":122},[112,496,153],{"class":125},[112,498,499],{"class":156}," route",[112,501,160],{"class":125},[112,503,504],{"class":163}," useRoute",[112,506,462],{"class":129},[112,508,509,511,514,516,519,522],{"class":114,"line":143},[112,510,153],{"class":125},[112,512,513],{"class":156}," slug",[112,515,160],{"class":125},[112,517,518],{"class":129}," Array.",[112,520,521],{"class":163},"isArray",[112,523,524],{"class":129},"(route.params.slug)\n",[112,526,527,530,533,536,538,541],{"class":114,"line":150},[112,528,529],{"class":125},"  ?",[112,531,532],{"class":129}," route.params.slug.",[112,534,535],{"class":163},"join",[112,537,425],{"class":129},[112,539,540],{"class":136},"'\u002F'",[112,542,431],{"class":129},[112,544,545,548],{"class":114,"line":170},[112,546,547],{"class":125},"  :",[112,549,550],{"class":129}," route.params.slug;\n",[112,552,553],{"class":114,"line":182},[112,554,147],{"emptyLinePlaceholder":146},[112,556,557,559,562,565,568,571,574,577,579,582,584,587,590,593,596],{"class":114,"line":193},[112,558,153],{"class":125},[112,560,561],{"class":129}," { ",[112,563,564],{"class":222},"data",[112,566,567],{"class":129},": ",[112,569,570],{"class":156},"post",[112,572,573],{"class":129}," } ",[112,575,576],{"class":125},"=",[112,578,419],{"class":125},[112,580,581],{"class":163}," useAsyncData",[112,583,425],{"class":129},[112,585,586],{"class":136},"`post-${",[112,588,589],{"class":129},"slug",[112,591,592],{"class":136},"}`",[112,594,595],{"class":129},", () ",[112,597,598],{"class":125},"=>\n",[112,600,601,604,606,608,611,614,616,619,621,623,625,628],{"class":114,"line":205},[112,602,603],{"class":163},"  queryCollection",[112,605,425],{"class":129},[112,607,428],{"class":136},[112,609,610],{"class":129},").",[112,612,613],{"class":163},"path",[112,615,425],{"class":129},[112,617,618],{"class":136},"`\u002Fblog\u002F${",[112,620,589],{"class":129},[112,622,592],{"class":136},[112,624,610],{"class":129},[112,626,627],{"class":163},"first",[112,629,630],{"class":129},"()\n",[112,632,633],{"class":114,"line":241},[112,634,635],{"class":129},");\n",[11,637,639],{"id":638},"unocss-への移行","UnoCSS への移行",[15,641,642],{},"Astro では Tailwind CSS v4 を使っていましたが、Nuxt への移行にあわせて UnoCSS に変更しました。",[15,644,645,646,649],{},"UnoCSS はオンデマンドで CSS を生成するため、使用したユーティリティクラスだけがバンドルに含まれます。",[90,647,648],{},"presetWind4"," で Tailwind v4 互換のユーティリティが使えます。",[103,651,653],{"className":105,"code":652,"language":107,"meta":108,"style":108},"\u002F\u002F uno.config.ts\nimport { defineConfig, presetWind4 } from 'unocss';\n\nexport default defineConfig({\n  presets: [presetWind4()],\n});\n",[90,654,655,660,674,678,689,699],{"__ignoreMap":108},[112,656,657],{"class":114,"line":115},[112,658,659],{"class":118},"\u002F\u002F uno.config.ts\n",[112,661,662,664,667,669,672],{"class":114,"line":122},[112,663,126],{"class":125},[112,665,666],{"class":129}," { defineConfig, presetWind4 } ",[112,668,133],{"class":125},[112,670,671],{"class":136}," 'unocss'",[112,673,140],{"class":129},[112,675,676],{"class":114,"line":143},[112,677,147],{"emptyLinePlaceholder":146},[112,679,680,682,684,687],{"class":114,"line":150},[112,681,288],{"class":125},[112,683,291],{"class":125},[112,685,686],{"class":163}," defineConfig",[112,688,167],{"class":129},[112,690,691,694,696],{"class":114,"line":170},[112,692,693],{"class":129},"  presets: [",[112,695,648],{"class":163},[112,697,698],{"class":129},"()],\n",[112,700,701],{"class":114,"line":182},[112,702,250],{"class":129},[15,704,705,706,709],{},"Nuxt への組み込みは ",[90,707,708],{},"@unocss\u002Fnuxt"," モジュールで完結します。",[103,711,713],{"className":105,"code":712,"language":107,"meta":108,"style":108},"\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@unocss\u002Fnuxt', '@nuxt\u002Fcontent'],\n});\n",[90,714,715,720,731,747],{"__ignoreMap":108},[112,716,717],{"class":114,"line":115},[112,718,719],{"class":118},"\u002F\u002F nuxt.config.ts\n",[112,721,722,724,726,729],{"class":114,"line":122},[112,723,288],{"class":125},[112,725,291],{"class":125},[112,727,728],{"class":163}," defineNuxtConfig",[112,730,167],{"class":129},[112,732,733,736,739,741,744],{"class":114,"line":143},[112,734,735],{"class":129},"  modules: [",[112,737,738],{"class":136},"'@unocss\u002Fnuxt'",[112,740,447],{"class":129},[112,742,743],{"class":136},"'@nuxt\u002Fcontent'",[112,745,746],{"class":129},"],\n",[112,748,749],{"class":114,"line":150},[112,750,250],{"class":129},[11,752,754],{"id":753},"vite-ツールチェーン","Vite+ ツールチェーン",[15,756,757,758,764,765,768],{},"このプロジェクトでは ",[759,760,50],"a",{"href":761,"rel":762},"https:\u002F\u002Fviteplus.dev",[763],"nofollow"," を使っています。Vite+は Vite \u002F Rolldown \u002F Vitest \u002F Oxlint \u002F Oxfmt を統合した ",[90,766,767],{},"vp"," コマンド 1 つで全てのツールを扱えるツールチェーンです。",[103,770,774],{"className":771,"code":772,"language":773,"meta":108,"style":108},"language-bash shiki shiki-themes github-light github-dark","# 開発サーバー起動\nvp dev\n\n# チェック（フォーマット・Lint・型チェック）\nvp check\n\n# テスト実行\nvp test\n","bash",[90,775,776,781,788,792,797,804,808,813],{"__ignoreMap":108},[112,777,778],{"class":114,"line":115},[112,779,780],{"class":118},"# 開発サーバー起動\n",[112,782,783,785],{"class":114,"line":122},[112,784,767],{"class":163},[112,786,787],{"class":136}," dev\n",[112,789,790],{"class":114,"line":143},[112,791,147],{"emptyLinePlaceholder":146},[112,793,794],{"class":114,"line":150},[112,795,796],{"class":118},"# チェック（フォーマット・Lint・型チェック）\n",[112,798,799,801],{"class":114,"line":170},[112,800,767],{"class":163},[112,802,803],{"class":136}," check\n",[112,805,806],{"class":114,"line":182},[112,807,147],{"emptyLinePlaceholder":146},[112,809,810],{"class":114,"line":193},[112,811,812],{"class":118},"# テスト実行\n",[112,814,815,817],{"class":114,"line":205},[112,816,767],{"class":163},[112,818,819],{"class":136}," test\n",[15,821,822],{},"個別にツールをインストール・設定する必要がなく、統一されたインターフェースでプロジェクトを管理できます。",[11,824,825],{"id":825},"ダークモード",[15,827,828],{},"システムの color-scheme に合わせた自動ダークモードを実装しています。",[15,830,831,832,835],{},"FOUC（コンテンツのちらつき）を防ぐため、インライン script を ",[90,833,834],{},"\u003Chead>"," 内で実行しています。",[103,837,839],{"className":105,"code":838,"language":107,"meta":108,"style":108},"\u002F\u002F nuxt.config.ts\napp: {\n  head: {\n    script: [{\n      innerHTML: `\n        const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n        document.documentElement.classList[isDark ? 'add' : 'remove']('dark');\n      `,\n      tagPosition: 'head',\n    }],\n  },\n},\n",[90,840,841,845,853,860,868,876,881,886,893,903,908,912],{"__ignoreMap":108},[112,842,843],{"class":114,"line":115},[112,844,719],{"class":118},[112,846,847,850],{"class":114,"line":122},[112,848,849],{"class":163},"app",[112,851,852],{"class":129},": {\n",[112,854,855,858],{"class":114,"line":143},[112,856,857],{"class":163},"  head",[112,859,852],{"class":129},[112,861,862,865],{"class":114,"line":150},[112,863,864],{"class":163},"    script",[112,866,867],{"class":129},": [{\n",[112,869,870,873],{"class":114,"line":170},[112,871,872],{"class":129},"      innerHTML: ",[112,874,875],{"class":136},"`\n",[112,877,878],{"class":114,"line":182},[112,879,880],{"class":136},"        const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n",[112,882,883],{"class":114,"line":193},[112,884,885],{"class":136},"        document.documentElement.classList[isDark ? 'add' : 'remove']('dark');\n",[112,887,888,891],{"class":114,"line":205},[112,889,890],{"class":136},"      `",[112,892,179],{"class":129},[112,894,895,898,901],{"class":114,"line":241},[112,896,897],{"class":129},"      tagPosition: ",[112,899,900],{"class":136},"'head'",[112,902,179],{"class":129},[112,904,905],{"class":114,"line":247},[112,906,907],{"class":129},"    }],\n",[112,909,910],{"class":114,"line":351},[112,911,376],{"class":129},[112,913,914],{"class":114,"line":361},[112,915,916],{"class":129},"},\n",[11,918,919],{"id":919},"まとめ",[15,921,922],{},"Astro から Nuxt Content v3 への移行で、Vue エコシステムに統一された環境を構築できました。",[31,924,925,930,935],{},[34,926,927,929],{},[27,928,44],{}," の SQLite ベースのクエリは高速で型安全",[34,931,932,934],{},[27,933,63],{}," のオンデマンド生成で CSS バンドルが最小化",[34,936,937,939],{},[27,938,50],{}," で複数ツールの設定を一元管理",[15,941,942],{},"Vue が好きな方には Nuxt Content の組み合わせを強くおすすめします。",[944,945,946],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":108,"searchDepth":122,"depth":122,"links":948},[949,950,951,956,957,958,959],{"id":13,"depth":122,"text":13},{"id":23,"depth":122,"text":23},{"id":80,"depth":122,"text":81,"children":952},[953,954,955],{"id":85,"depth":143,"text":85},{"id":387,"depth":143,"text":387},{"id":468,"depth":143,"text":468},{"id":638,"depth":122,"text":639},{"id":753,"depth":122,"text":754},{"id":825,"depth":122,"text":825},{"id":919,"depth":122,"text":919},"2026-03-14","Astro 5 から Nuxt Content v3 + UnoCSS + Vite+ に移行した経緯と技術スタックの紹介です。","md",{},"\u002Fblog\u002Fnuxt-content-renewal",{"title":6,"description":961},"blog\u002Fnuxt-content-renewal","HMUgEHjcqGT6PHvlIGOALNvMYuxFxCANN5ZPFyd2LyQ",{"id":969,"title":970,"body":971,"date":2902,"description":2903,"extension":962,"meta":2904,"navigation":146,"path":2908,"seo":2909,"stem":2910,"__hash__":2911},"blog\u002Fblog\u002Fblog-tech-stack.md","個人ブログを支える技術",{"type":8,"value":972,"toc":2876},[973,976,979,982,985,988,993,1007,1012,1026,1030,1050,1055,1093,1097,1105,1108,1142,1145,1152,1306,1309,1312,1407,1410,1414,1417,1546,1549,1552,1559,1562,1582,1585,1588,1790,1793,1796,1802,1821,1831,1835,1841,1892,1895,1899,1902,1907,2155,2158,2162,2165,2179,2489,2492,2494,2500,2532,2535,2596,2599,2606,2609,2641,2645,2653,2659,2685,2719,2722,2725,2728,2753,2757,2765,2768,2793,2796,2803,2828,2834,2836,2839,2842,2867,2870,2873],[11,974,975],{"id":975},"個人ブログを始めました",[15,977,978],{},"これまで Zenn などのプラットフォームで記事を書いてきましたが、自分のドメインで自由に発信できる場所が欲しくなり、このブログを立ち上げました。",[15,980,981],{},"せっかくなので、このブログを支える技術スタックについて紹介します。",[11,983,984],{"id":984},"全体構成",[15,986,987],{},"このブログは以下の技術で構築されています",[15,989,990],{},[27,991,992],{},"コア技術",[31,994,995,1001],{},[34,996,997,1000],{},[27,998,999],{},"Astro 5"," - 超高速な静的サイトジェネレーター",[34,1002,1003,1006],{},[27,1004,1005],{},"TypeScript"," - 型安全な開発環境",[15,1008,1009],{},[27,1010,1011],{},"コンテンツ管理",[31,1013,1014,1020],{},[34,1015,1016,1019],{},[27,1017,1018],{},"Content Collections"," - Zodによる型安全なMarkdown管理",[34,1021,1022,1025],{},[27,1023,1024],{},"@astrojs\u002Fmdx"," - MDX形式のサポート",[15,1027,1028],{},[27,1029,56],{},[31,1031,1032,1038,1044],{},[34,1033,1034,1037],{},[27,1035,1036],{},"Tailwind CSS v4"," - ユーティリティファーストCSS",[34,1039,1040,1043],{},[27,1041,1042],{},"@tailwindcss\u002Ftypography"," - 記事本文の美しいタイポグラフィ",[34,1045,1046,1049],{},[27,1047,1048],{},"CSS変数"," - ダークモード対応のテーマシステム",[15,1051,1052],{},[27,1053,1054],{},"その他",[31,1056,1057,1063,1069,1075,1081,1087],{},[34,1058,1059,1062],{},[27,1060,1061],{},"astro-icon"," - Iconifyベースのアイコンシステム",[34,1064,1065,1068],{},[27,1066,1067],{},"@astrojs\u002Fsitemap"," - SEO最適化",[34,1070,1071,1074],{},[27,1072,1073],{},"@astrojs\u002Frss"," - RSSフィード生成",[34,1076,1077,1080],{},[27,1078,1079],{},"textlint"," - 日本語文章の品質チェック",[34,1082,1083,1086],{},[27,1084,1085],{},"satori + sharp"," - 動的OGP画像生成",[34,1088,1089,1092],{},[27,1090,1091],{},"wrangler"," - Cloudflare Workersへのデプロイ",[11,1094,1096],{"id":1095},"astro-5-content-collections","Astro 5 + Content Collections",[15,1098,1099,1100,1104],{},"フレームワークとして ",[759,1101,999],{"href":1102,"rel":1103},"https:\u002F\u002Fastro.build",[763]," を採用しています。",[15,1106,1107],{},"Astro を選んだ理由は以下のとおりです",[31,1109,1110,1116,1125,1130,1136],{},[34,1111,1112,1115],{},[27,1113,1114],{},"超高速"," - デフォルトでゼロJavaScript、必要な部分だけクライアントサイドJS",[34,1117,1118,1121,1122,1124],{},[27,1119,1120],{},"ファイルベースルーティング"," - ",[90,1123,474],{}," ディレクトリで直感的にルーティングを構築",[34,1126,1127,1129],{},[27,1128,1018],{}," - 型安全なMarkdown\u002FMDX管理",[34,1131,1132,1135],{},[27,1133,1134],{},"柔軟性"," - 必要に応じてReact、Vue、Svelteなどを組み合わせ可能",[34,1137,1138,1141],{},[27,1139,1140],{},"優れたDX"," - TypeScript完全サポート、高速なHMR",[83,1143,1018],{"id":1144},"content-collections",[15,1146,1147,1148,1151],{},"ブログ記事は ",[90,1149,1150],{},"src\u002Fcontent\u002Fblog\u002F"," ディレクトリで管理しています。Content Collections は Markdown ファイルを型安全に扱えるコンテンツ管理システムです。",[103,1153,1155],{"className":105,"code":1154,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fcontent\u002Fconfig.ts\nimport { defineCollection, z } from 'astro:content';\n\nconst blogCollection = defineCollection({\n  type: 'content',\n  schema: z.object({\n    title: z.string(),\n    description: z.string(),\n    date: z.string().transform((str) => new Date(str)),\n    tags: z.array(z.string()).default([]),\n  }),\n});\n\nexport const collections = {\n  blog: blogCollection,\n};\n",[90,1156,1157,1161,1173,1177,1189,1197,1205,1213,1222,1246,1268,1272,1276,1280,1295,1300],{"__ignoreMap":108},[112,1158,1159],{"class":114,"line":115},[112,1160,119],{"class":118},[112,1162,1163,1165,1167,1169,1171],{"class":114,"line":122},[112,1164,126],{"class":125},[112,1166,130],{"class":129},[112,1168,133],{"class":125},[112,1170,137],{"class":136},[112,1172,140],{"class":129},[112,1174,1175],{"class":114,"line":143},[112,1176,147],{"emptyLinePlaceholder":146},[112,1178,1179,1181,1183,1185,1187],{"class":114,"line":150},[112,1180,153],{"class":125},[112,1182,157],{"class":156},[112,1184,160],{"class":125},[112,1186,164],{"class":163},[112,1188,167],{"class":129},[112,1190,1191,1193,1195],{"class":114,"line":170},[112,1192,173],{"class":129},[112,1194,176],{"class":136},[112,1196,179],{"class":129},[112,1198,1199,1201,1203],{"class":114,"line":182},[112,1200,185],{"class":129},[112,1202,188],{"class":163},[112,1204,167],{"class":129},[112,1206,1207,1209,1211],{"class":114,"line":193},[112,1208,196],{"class":129},[112,1210,199],{"class":163},[112,1212,202],{"class":129},[112,1214,1215,1218,1220],{"class":114,"line":205},[112,1216,1217],{"class":129},"    description: z.",[112,1219,199],{"class":163},[112,1221,202],{"class":129},[112,1223,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244],{"class":114,"line":241},[112,1225,208],{"class":129},[112,1227,199],{"class":163},[112,1229,213],{"class":129},[112,1231,216],{"class":163},[112,1233,219],{"class":129},[112,1235,223],{"class":222},[112,1237,226],{"class":129},[112,1239,229],{"class":125},[112,1241,232],{"class":125},[112,1243,235],{"class":163},[112,1245,238],{"class":129},[112,1247,1248,1251,1254,1257,1259,1262,1265],{"class":114,"line":247},[112,1249,1250],{"class":129},"    tags: z.",[112,1252,1253],{"class":163},"array",[112,1255,1256],{"class":129},"(z.",[112,1258,199],{"class":163},[112,1260,1261],{"class":129},"()).",[112,1263,1264],{"class":163},"default",[112,1266,1267],{"class":129},"([]),\n",[112,1269,1270],{"class":114,"line":351},[112,1271,244],{"class":129},[112,1273,1274],{"class":114,"line":361},[112,1275,250],{"class":129},[112,1277,1278],{"class":114,"line":367},[112,1279,147],{"emptyLinePlaceholder":146},[112,1281,1282,1284,1287,1290,1292],{"class":114,"line":373},[112,1283,288],{"class":125},[112,1285,1286],{"class":125}," const",[112,1288,1289],{"class":156}," collections",[112,1291,160],{"class":125},[112,1293,1294],{"class":129}," {\n",[112,1296,1297],{"class":114,"line":379},[112,1298,1299],{"class":129},"  blog: blogCollection,\n",[112,1301,1303],{"class":114,"line":1302},16,[112,1304,1305],{"class":129},"};\n",[15,1307,1308],{},"Zodによる型チェックと、ビルド時のバリデーションで安全な記事管理が可能です。",[83,1310,1311],{"id":1311},"記事の取得",[103,1313,1315],{"className":105,"code":1314,"language":107,"meta":108,"style":108},"import { getCollection } from 'astro:content';\n\nconst allPosts = await getCollection('blog');\nconst sortedPosts = allPosts.sort((a, b) =>\n  b.data.date.getTime() - a.data.date.getTime()\n);\n",[90,1316,1317,1330,1334,1354,1382,1403],{"__ignoreMap":108},[112,1318,1319,1321,1324,1326,1328],{"class":114,"line":115},[112,1320,126],{"class":125},[112,1322,1323],{"class":129}," { getCollection } ",[112,1325,133],{"class":125},[112,1327,137],{"class":136},[112,1329,140],{"class":129},[112,1331,1332],{"class":114,"line":122},[112,1333,147],{"emptyLinePlaceholder":146},[112,1335,1336,1338,1341,1343,1345,1348,1350,1352],{"class":114,"line":143},[112,1337,153],{"class":125},[112,1339,1340],{"class":156}," allPosts",[112,1342,160],{"class":125},[112,1344,419],{"class":125},[112,1346,1347],{"class":163}," getCollection",[112,1349,425],{"class":129},[112,1351,428],{"class":136},[112,1353,635],{"class":129},[112,1355,1356,1358,1361,1363,1366,1369,1371,1373,1375,1378,1380],{"class":114,"line":150},[112,1357,153],{"class":125},[112,1359,1360],{"class":156}," sortedPosts",[112,1362,160],{"class":125},[112,1364,1365],{"class":129}," allPosts.",[112,1367,1368],{"class":163},"sort",[112,1370,219],{"class":129},[112,1372,759],{"class":222},[112,1374,447],{"class":129},[112,1376,1377],{"class":222},"b",[112,1379,226],{"class":129},[112,1381,598],{"class":125},[112,1383,1384,1387,1390,1393,1396,1399,1401],{"class":114,"line":170},[112,1385,1386],{"class":129},"  b.data.date.",[112,1388,1389],{"class":163},"getTime",[112,1391,1392],{"class":129},"() ",[112,1394,1395],{"class":125},"-",[112,1397,1398],{"class":129}," a.data.date.",[112,1400,1389],{"class":163},[112,1402,630],{"class":129},[112,1404,1405],{"class":114,"line":182},[112,1406,635],{"class":129},[15,1408,1409],{},"シンプルで直感的なAPIで記事を取得できます。",[11,1411,1413],{"id":1412},"astroコンポーネント","Astroコンポーネント",[15,1415,1416],{},"UI実装には Astro コンポーネントを使用しています。",[103,1418,1422],{"className":1419,"code":1420,"language":1421,"meta":108,"style":108},"language-astro shiki shiki-themes github-light github-dark","---\nimport type { CollectionEntry } from 'astro:content';\n\ninterface Props {\n  post: CollectionEntry\u003C'blog'>;\n}\n\nconst { post } = Astro.props;\n\nfunction formatDate(date: Date): string {\n  return date.toLocaleDateString('ja-JP', {\n    year: 'numeric',\n    month: 'short',\n    day: 'numeric',\n  });\n}\n---\n\n\u003Carticle class=\"group h-full\">\n  \u003Ca href={`\u002Fblog\u002F${post.slug}`}>\n    \u003Ch3>{post.data.title}\u003C\u002Fh3>\n    \u003Cp>{post.data.description}\u003C\u002Fp>\n  \u003C\u002Fa>\n\u003C\u002Farticle>\n","astro",[90,1423,1424,1429,1434,1438,1443,1448,1453,1457,1462,1466,1471,1476,1481,1486,1491,1496,1500,1505,1510,1516,1522,1528,1534,1540],{"__ignoreMap":108},[112,1425,1426],{"class":114,"line":115},[112,1427,1428],{},"---\n",[112,1430,1431],{"class":114,"line":122},[112,1432,1433],{},"import type { CollectionEntry } from 'astro:content';\n",[112,1435,1436],{"class":114,"line":143},[112,1437,147],{"emptyLinePlaceholder":146},[112,1439,1440],{"class":114,"line":150},[112,1441,1442],{},"interface Props {\n",[112,1444,1445],{"class":114,"line":170},[112,1446,1447],{},"  post: CollectionEntry\u003C'blog'>;\n",[112,1449,1450],{"class":114,"line":182},[112,1451,1452],{},"}\n",[112,1454,1455],{"class":114,"line":193},[112,1456,147],{"emptyLinePlaceholder":146},[112,1458,1459],{"class":114,"line":205},[112,1460,1461],{},"const { post } = Astro.props;\n",[112,1463,1464],{"class":114,"line":241},[112,1465,147],{"emptyLinePlaceholder":146},[112,1467,1468],{"class":114,"line":247},[112,1469,1470],{},"function formatDate(date: Date): string {\n",[112,1472,1473],{"class":114,"line":351},[112,1474,1475],{},"  return date.toLocaleDateString('ja-JP', {\n",[112,1477,1478],{"class":114,"line":361},[112,1479,1480],{},"    year: 'numeric',\n",[112,1482,1483],{"class":114,"line":367},[112,1484,1485],{},"    month: 'short',\n",[112,1487,1488],{"class":114,"line":373},[112,1489,1490],{},"    day: 'numeric',\n",[112,1492,1493],{"class":114,"line":379},[112,1494,1495],{},"  });\n",[112,1497,1498],{"class":114,"line":1302},[112,1499,1452],{},[112,1501,1503],{"class":114,"line":1502},17,[112,1504,1428],{},[112,1506,1508],{"class":114,"line":1507},18,[112,1509,147],{"emptyLinePlaceholder":146},[112,1511,1513],{"class":114,"line":1512},19,[112,1514,1515],{},"\u003Carticle class=\"group h-full\">\n",[112,1517,1519],{"class":114,"line":1518},20,[112,1520,1521],{},"  \u003Ca href={`\u002Fblog\u002F${post.slug}`}>\n",[112,1523,1525],{"class":114,"line":1524},21,[112,1526,1527],{},"    \u003Ch3>{post.data.title}\u003C\u002Fh3>\n",[112,1529,1531],{"class":114,"line":1530},22,[112,1532,1533],{},"    \u003Cp>{post.data.description}\u003C\u002Fp>\n",[112,1535,1537],{"class":114,"line":1536},23,[112,1538,1539],{},"  \u003C\u002Fa>\n",[112,1541,1543],{"class":114,"line":1542},24,[112,1544,1545],{},"\u003C\u002Farticle>\n",[15,1547,1548],{},"HTMLライクなシンプルな構文で、型安全なコンポーネントを書くことができます。",[11,1550,1036],{"id":1551},"tailwind-css-v4",[15,1553,1554,1555,1104],{},"スタイリングには ",[759,1556,1036],{"href":1557,"rel":1558},"https:\u002F\u002Ftailwindcss.com",[763],[15,1560,1561],{},"Tailwind CSS v4 の主な特徴：",[31,1563,1564,1570,1576],{},[34,1565,1566,1569],{},[27,1567,1568],{},"Viteプラグイン"," - PostCSS不要で、Vite統合が簡単に",[34,1571,1572,1575],{},[27,1573,1574],{},"CSS変数ベース"," - カスタムプロパティを活用したテーマシステム",[34,1577,1578,1581],{},[27,1579,1580],{},"ゼロコンフィグ"," - 設定ファイルなしでも動作",[83,1583,1584],{"id":1584},"カスタムカラーテーマ",[15,1586,1587],{},"CSS変数を使って、ライト\u002Fダークモードのカラーテーマを実装しています。",[103,1589,1593],{"className":1590,"code":1591,"language":1592,"meta":108,"style":108},"language-css shiki shiki-themes github-light github-dark","@theme {\n  \u002F* Surface Colors *\u002F\n  --color-surface: #fafafa;\n  --color-text-primary: #18181b;\n\n  \u002F* Primary - Purple Gradient *\u002F\n  --color-primary: #8b5cf6;\n\n  \u002F* Gradients *\u002F\n  --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n  --gradient-accent: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);\n\n  --container-max: 1400px;\n}\n\n.dark {\n  --color-surface: #09090b;\n  --color-text-primary: #fafafa;\n  --color-primary: #a78bfa;\n  --gradient-primary: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%);\n}\n","css",[90,1594,1595,1602,1607,1616,1626,1630,1635,1645,1649,1654,1671,1686,1690,1695,1699,1703,1710,1722,1734,1746,1786],{"__ignoreMap":108},[112,1596,1597,1600],{"class":114,"line":115},[112,1598,1599],{"class":125},"@theme",[112,1601,1294],{"class":129},[112,1603,1604],{"class":114,"line":122},[112,1605,1606],{"class":118},"  \u002F* Surface Colors *\u002F\n",[112,1608,1609,1612],{"class":114,"line":143},[112,1610,1611],{"class":129},"  --color-surface: ",[112,1613,1615],{"class":1614},"s7hpK","#fafafa;\n",[112,1617,1618,1621,1624],{"class":114,"line":150},[112,1619,1620],{"class":129},"  --color-text-primary: ",[112,1622,1623],{"class":1614},"#18181b",[112,1625,140],{"class":129},[112,1627,1628],{"class":114,"line":170},[112,1629,147],{"emptyLinePlaceholder":146},[112,1631,1632],{"class":114,"line":182},[112,1633,1634],{"class":118},"  \u002F* Primary - Purple Gradient *\u002F\n",[112,1636,1637,1640,1643],{"class":114,"line":193},[112,1638,1639],{"class":129},"  --color-primary: ",[112,1641,1642],{"class":1614},"#8b5cf6",[112,1644,140],{"class":129},[112,1646,1647],{"class":114,"line":205},[112,1648,147],{"emptyLinePlaceholder":146},[112,1650,1651],{"class":114,"line":241},[112,1652,1653],{"class":118},"  \u002F* Gradients *\u002F\n",[112,1655,1656,1659,1662,1665,1668],{"class":114,"line":247},[112,1657,1658],{"class":129},"  --gradient-primary: linear-gradient(135deg, ",[112,1660,1661],{"class":1614},"#667eea",[112,1663,1664],{"class":129}," 0%, ",[112,1666,1667],{"class":1614},"#764ba2",[112,1669,1670],{"class":129}," 100%);\n",[112,1672,1673,1676,1679,1681,1684],{"class":114,"line":351},[112,1674,1675],{"class":129},"  --gradient-accent: linear-gradient(135deg, ",[112,1677,1678],{"class":163},"#f093fb",[112,1680,1664],{"class":129},[112,1682,1683],{"class":163},"#f5576c",[112,1685,1670],{"class":129},[112,1687,1688],{"class":114,"line":361},[112,1689,147],{"emptyLinePlaceholder":146},[112,1691,1692],{"class":114,"line":367},[112,1693,1694],{"class":129},"  --container-max: 1400px;\n",[112,1696,1697],{"class":114,"line":373},[112,1698,1452],{"class":129},[112,1700,1701],{"class":114,"line":379},[112,1702,147],{"emptyLinePlaceholder":146},[112,1704,1705,1708],{"class":114,"line":1302},[112,1706,1707],{"class":163},".dark",[112,1709,1294],{"class":129},[112,1711,1712,1715,1717,1720],{"class":114,"line":1502},[112,1713,1714],{"class":222},"  --color-surface",[112,1716,567],{"class":129},[112,1718,1719],{"class":156},"#09090b",[112,1721,140],{"class":129},[112,1723,1724,1727,1729,1732],{"class":114,"line":1507},[112,1725,1726],{"class":222},"  --color-text-primary",[112,1728,567],{"class":129},[112,1730,1731],{"class":156},"#fafafa",[112,1733,140],{"class":129},[112,1735,1736,1739,1741,1744],{"class":114,"line":1512},[112,1737,1738],{"class":222},"  --color-primary",[112,1740,567],{"class":129},[112,1742,1743],{"class":156},"#a78bfa",[112,1745,140],{"class":129},[112,1747,1748,1751,1753,1756,1758,1761,1764,1766,1768,1771,1774,1776,1779,1782,1784],{"class":114,"line":1518},[112,1749,1750],{"class":222},"  --gradient-primary",[112,1752,567],{"class":129},[112,1754,1755],{"class":156},"linear-gradient",[112,1757,425],{"class":129},[112,1759,1760],{"class":156},"135",[112,1762,1763],{"class":125},"deg",[112,1765,447],{"class":129},[112,1767,1642],{"class":156},[112,1769,1770],{"class":156}," 0",[112,1772,1773],{"class":125},"%",[112,1775,447],{"class":129},[112,1777,1778],{"class":156},"#ec4899",[112,1780,1781],{"class":156}," 100",[112,1783,1773],{"class":125},[112,1785,635],{"class":129},[112,1787,1788],{"class":114,"line":1524},[112,1789,1452],{"class":129},[15,1791,1792],{},"localStorageとMutationObserverを使用して、ユーザーの好みに応じた自動テーマ切り替えを実装しています。",[83,1794,1042],{"id":1795},"tailwindcsstypography",[15,1797,1798,1799,1801],{},"記事本文のスタイリングには ",[90,1800,1042],{}," プラグインを使用しています。",[103,1803,1805],{"className":1419,"code":1804,"language":1421,"meta":108,"style":108},"\u003Carticle class=\"prose dark:prose-invert max-w-none\">\n  \u003CContent \u002F>\n\u003C\u002Farticle>\n",[90,1806,1807,1812,1817],{"__ignoreMap":108},[112,1808,1809],{"class":114,"line":115},[112,1810,1811],{},"\u003Carticle class=\"prose dark:prose-invert max-w-none\">\n",[112,1813,1814],{"class":114,"line":122},[112,1815,1816],{},"  \u003CContent \u002F>\n",[112,1818,1819],{"class":114,"line":143},[112,1820,1545],{},[15,1822,1823,1826,1827,1830],{},[90,1824,1825],{},"prose"," クラスを適用するだけで、見出し、段落、リスト、コードブロックなど、あらゆる要素に美しいタイポグラフィが適用されます。",[90,1828,1829],{},"dark:prose-invert"," でダークモードにも自動対応します。",[11,1832,1834],{"id":1833},"seo対策","SEO対策",[15,1836,1837,1838,1840],{},"SEO対策には ",[90,1839,1067],{}," を使用しています。",[103,1842,1844],{"className":105,"code":1843,"language":107,"meta":108,"style":108},"\u002F\u002F astro.config.mjs\nexport default defineConfig({\n  site: 'https:\u002F\u002Fnao-dev.netlify.app\u002F',\n  integrations: [\n    sitemap(),\n  ],\n});\n",[90,1845,1846,1851,1861,1871,1876,1883,1888],{"__ignoreMap":108},[112,1847,1848],{"class":114,"line":115},[112,1849,1850],{"class":118},"\u002F\u002F astro.config.mjs\n",[112,1852,1853,1855,1857,1859],{"class":114,"line":122},[112,1854,288],{"class":125},[112,1856,291],{"class":125},[112,1858,686],{"class":163},[112,1860,167],{"class":129},[112,1862,1863,1866,1869],{"class":114,"line":143},[112,1864,1865],{"class":129},"  site: ",[112,1867,1868],{"class":136},"'https:\u002F\u002Fnao-dev.netlify.app\u002F'",[112,1870,179],{"class":129},[112,1872,1873],{"class":114,"line":150},[112,1874,1875],{"class":129},"  integrations: [\n",[112,1877,1878,1881],{"class":114,"line":170},[112,1879,1880],{"class":163},"    sitemap",[112,1882,202],{"class":129},[112,1884,1885],{"class":114,"line":182},[112,1886,1887],{"class":129},"  ],\n",[112,1889,1890],{"class":114,"line":193},[112,1891,250],{"class":129},[15,1893,1894],{},"自動的にsitemap.xmlが生成され、検索エンジンのクローラーがサイトを効率的にインデックスできます。",[11,1896,1898],{"id":1897},"rssフィード-astrojsrss","RSSフィード - @astrojs\u002Frss",[15,1900,1901],{},"購読者のために、RSSフィードを提供しています。",[15,1903,1904,1906],{},[90,1905,1073],{}," を使用することで、簡単にRSSフィードを生成できます。",[103,1908,1912],{"className":1909,"code":1910,"language":1911,"meta":108,"style":108},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F src\u002Fpages\u002Frss.xml.js\nimport rss from '@astrojs\u002Frss';\nimport { getCollection } from 'astro:content';\n\nexport async function GET(context) {\n  const posts = await getCollection('blog');\n\n  const sortedPosts = posts.sort((a, b) =>\n    b.data.date.getTime() - a.data.date.getTime()\n  );\n\n  return rss({\n    title: 'nao.dev',\n    description: 'Front-end Developer loving Vue ecosystem',\n    site: context.site,\n    items: sortedPosts.map((post) => ({\n      title: post.data.title,\n      description: post.data.description,\n      pubDate: post.data.date,\n      link: `\u002Fblog\u002F${post.slug}\u002F`,\n    })),\n    customData: `\u003Clanguage>ja\u003C\u002Flanguage>`,\n  });\n}\n","javascript",[90,1913,1914,1919,1933,1945,1949,1970,1989,1993,2018,2035,2040,2044,2054,2064,2074,2079,2098,2103,2108,2113,2132,2137,2147,2151],{"__ignoreMap":108},[112,1915,1916],{"class":114,"line":115},[112,1917,1918],{"class":118},"\u002F\u002F src\u002Fpages\u002Frss.xml.js\n",[112,1920,1921,1923,1926,1928,1931],{"class":114,"line":122},[112,1922,126],{"class":125},[112,1924,1925],{"class":129}," rss ",[112,1927,133],{"class":125},[112,1929,1930],{"class":136}," '@astrojs\u002Frss'",[112,1932,140],{"class":129},[112,1934,1935,1937,1939,1941,1943],{"class":114,"line":143},[112,1936,126],{"class":125},[112,1938,1323],{"class":129},[112,1940,133],{"class":125},[112,1942,137],{"class":136},[112,1944,140],{"class":129},[112,1946,1947],{"class":114,"line":150},[112,1948,147],{"emptyLinePlaceholder":146},[112,1950,1951,1953,1956,1959,1962,1964,1967],{"class":114,"line":170},[112,1952,288],{"class":125},[112,1954,1955],{"class":125}," async",[112,1957,1958],{"class":125}," function",[112,1960,1961],{"class":163}," GET",[112,1963,425],{"class":129},[112,1965,1966],{"class":222},"context",[112,1968,1969],{"class":129},") {\n",[112,1971,1972,1975,1977,1979,1981,1983,1985,1987],{"class":114,"line":182},[112,1973,1974],{"class":125},"  const",[112,1976,414],{"class":156},[112,1978,160],{"class":125},[112,1980,419],{"class":125},[112,1982,1347],{"class":163},[112,1984,425],{"class":129},[112,1986,428],{"class":136},[112,1988,635],{"class":129},[112,1990,1991],{"class":114,"line":193},[112,1992,147],{"emptyLinePlaceholder":146},[112,1994,1995,1997,1999,2001,2004,2006,2008,2010,2012,2014,2016],{"class":114,"line":205},[112,1996,1974],{"class":125},[112,1998,1360],{"class":156},[112,2000,160],{"class":125},[112,2002,2003],{"class":129}," posts.",[112,2005,1368],{"class":163},[112,2007,219],{"class":129},[112,2009,759],{"class":222},[112,2011,447],{"class":129},[112,2013,1377],{"class":222},[112,2015,226],{"class":129},[112,2017,598],{"class":125},[112,2019,2020,2023,2025,2027,2029,2031,2033],{"class":114,"line":241},[112,2021,2022],{"class":129},"    b.data.date.",[112,2024,1389],{"class":163},[112,2026,1392],{"class":129},[112,2028,1395],{"class":125},[112,2030,1398],{"class":129},[112,2032,1389],{"class":163},[112,2034,630],{"class":129},[112,2036,2037],{"class":114,"line":247},[112,2038,2039],{"class":129},"  );\n",[112,2041,2042],{"class":114,"line":351},[112,2043,147],{"emptyLinePlaceholder":146},[112,2045,2046,2049,2052],{"class":114,"line":361},[112,2047,2048],{"class":125},"  return",[112,2050,2051],{"class":163}," rss",[112,2053,167],{"class":129},[112,2055,2056,2059,2062],{"class":114,"line":367},[112,2057,2058],{"class":129},"    title: ",[112,2060,2061],{"class":136},"'nao.dev'",[112,2063,179],{"class":129},[112,2065,2066,2069,2072],{"class":114,"line":373},[112,2067,2068],{"class":129},"    description: ",[112,2070,2071],{"class":136},"'Front-end Developer loving Vue ecosystem'",[112,2073,179],{"class":129},[112,2075,2076],{"class":114,"line":379},[112,2077,2078],{"class":129},"    site: context.site,\n",[112,2080,2081,2084,2087,2089,2091,2093,2095],{"class":114,"line":1302},[112,2082,2083],{"class":129},"    items: sortedPosts.",[112,2085,2086],{"class":163},"map",[112,2088,219],{"class":129},[112,2090,570],{"class":222},[112,2092,226],{"class":129},[112,2094,229],{"class":125},[112,2096,2097],{"class":129}," ({\n",[112,2099,2100],{"class":114,"line":1502},[112,2101,2102],{"class":129},"      title: post.data.title,\n",[112,2104,2105],{"class":114,"line":1507},[112,2106,2107],{"class":129},"      description: post.data.description,\n",[112,2109,2110],{"class":114,"line":1512},[112,2111,2112],{"class":129},"      pubDate: post.data.date,\n",[112,2114,2115,2118,2120,2122,2125,2127,2130],{"class":114,"line":1518},[112,2116,2117],{"class":129},"      link: ",[112,2119,618],{"class":136},[112,2121,570],{"class":129},[112,2123,2124],{"class":136},".",[112,2126,589],{"class":129},[112,2128,2129],{"class":136},"}\u002F`",[112,2131,179],{"class":129},[112,2133,2134],{"class":114,"line":1524},[112,2135,2136],{"class":129},"    })),\n",[112,2138,2139,2142,2145],{"class":114,"line":1530},[112,2140,2141],{"class":129},"    customData: ",[112,2143,2144],{"class":136},"`\u003Clanguage>ja\u003C\u002Flanguage>`",[112,2146,179],{"class":129},[112,2148,2149],{"class":114,"line":1536},[112,2150,1495],{"class":129},[112,2152,2153],{"class":114,"line":1542},[112,2154,1452],{"class":129},[15,2156,2157],{},"Content Collectionsと連携することで、記事の追加・更新時に自動的にRSSフィードも更新されます。",[11,2159,2161],{"id":2160},"動的ogp画像生成-satori-sharp","動的OGP画像生成 - satori + sharp",[15,2163,2164],{},"各記事ごとに動的にOGP画像を生成しています。",[15,2166,2167,2172,2173,2178],{},[759,2168,2171],{"href":2169,"rel":2170},"https:\u002F\u002Fgithub.com\u002Fvercel\u002Fsatori",[763],"satori"," はVercelが開発したライブラリで、HTMLとCSSをSVGに変換できます。これと ",[759,2174,2177],{"href":2175,"rel":2176},"https:\u002F\u002Fsharp.pixelplumbing.com\u002F",[763],"sharp"," を組み合わせることで、美しいOGP画像を自動生成できます。",[103,2180,2182],{"className":105,"code":2181,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fpages\u002Fog\u002F[...slug].png.ts\nimport satori from 'satori';\nimport sharp from 'sharp';\nimport { getCollection } from 'astro:content';\n\nexport const GET: APIRoute = async ({ props }) => {\n  const { post } = props;\n\n  \u002F\u002F Satoriでデザインを定義\n  const svg = await satori(\n    {\n      type: 'div',\n      props: {\n        style: {\n          background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',\n          \u002F\u002F ... スタイル定義\n        },\n        children: [\n          { type: 'div', props: { children: post.data.title } },\n        ],\n      },\n    },\n    { width: 1200, height: 630 }\n  );\n\n  \u002F\u002F SharpでSVGをPNGに変換\n  const png = await sharp(Buffer.from(svg)).png().toBuffer();\n\n  return new Response(png, {\n    headers: { 'Content-Type': 'image\u002Fpng' },\n  });\n};\n",[90,2183,2184,2189,2203,2217,2229,2233,2264,2279,2283,2288,2305,2310,2319,2324,2329,2339,2344,2349,2354,2364,2369,2374,2379,2396,2400,2405,2411,2444,2449,2462,2479,2484],{"__ignoreMap":108},[112,2185,2186],{"class":114,"line":115},[112,2187,2188],{"class":118},"\u002F\u002F src\u002Fpages\u002Fog\u002F[...slug].png.ts\n",[112,2190,2191,2193,2196,2198,2201],{"class":114,"line":122},[112,2192,126],{"class":125},[112,2194,2195],{"class":129}," satori ",[112,2197,133],{"class":125},[112,2199,2200],{"class":136}," 'satori'",[112,2202,140],{"class":129},[112,2204,2205,2207,2210,2212,2215],{"class":114,"line":143},[112,2206,126],{"class":125},[112,2208,2209],{"class":129}," sharp ",[112,2211,133],{"class":125},[112,2213,2214],{"class":136}," 'sharp'",[112,2216,140],{"class":129},[112,2218,2219,2221,2223,2225,2227],{"class":114,"line":150},[112,2220,126],{"class":125},[112,2222,1323],{"class":129},[112,2224,133],{"class":125},[112,2226,137],{"class":136},[112,2228,140],{"class":129},[112,2230,2231],{"class":114,"line":170},[112,2232,147],{"emptyLinePlaceholder":146},[112,2234,2235,2237,2239,2241,2244,2247,2249,2251,2254,2257,2260,2262],{"class":114,"line":182},[112,2236,288],{"class":125},[112,2238,1286],{"class":125},[112,2240,1961],{"class":163},[112,2242,2243],{"class":125},":",[112,2245,2246],{"class":163}," APIRoute",[112,2248,160],{"class":125},[112,2250,1955],{"class":125},[112,2252,2253],{"class":129}," ({ ",[112,2255,2256],{"class":222},"props",[112,2258,2259],{"class":129}," }) ",[112,2261,229],{"class":125},[112,2263,1294],{"class":129},[112,2265,2266,2268,2270,2272,2274,2276],{"class":114,"line":193},[112,2267,1974],{"class":125},[112,2269,561],{"class":129},[112,2271,570],{"class":156},[112,2273,573],{"class":129},[112,2275,576],{"class":125},[112,2277,2278],{"class":129}," props;\n",[112,2280,2281],{"class":114,"line":205},[112,2282,147],{"emptyLinePlaceholder":146},[112,2284,2285],{"class":114,"line":241},[112,2286,2287],{"class":118},"  \u002F\u002F Satoriでデザインを定義\n",[112,2289,2290,2292,2295,2297,2299,2302],{"class":114,"line":247},[112,2291,1974],{"class":125},[112,2293,2294],{"class":156}," svg",[112,2296,160],{"class":125},[112,2298,419],{"class":125},[112,2300,2301],{"class":163}," satori",[112,2303,2304],{"class":129},"(\n",[112,2306,2307],{"class":114,"line":351},[112,2308,2309],{"class":129},"    {\n",[112,2311,2312,2314,2317],{"class":114,"line":361},[112,2313,315],{"class":129},[112,2315,2316],{"class":136},"'div'",[112,2318,179],{"class":129},[112,2320,2321],{"class":114,"line":367},[112,2322,2323],{"class":129},"      props: {\n",[112,2325,2326],{"class":114,"line":373},[112,2327,2328],{"class":129},"        style: {\n",[112,2330,2331,2334,2337],{"class":114,"line":379},[112,2332,2333],{"class":129},"          background: ",[112,2335,2336],{"class":136},"'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'",[112,2338,179],{"class":129},[112,2340,2341],{"class":114,"line":1302},[112,2342,2343],{"class":118},"          \u002F\u002F ... スタイル定義\n",[112,2345,2346],{"class":114,"line":1502},[112,2347,2348],{"class":129},"        },\n",[112,2350,2351],{"class":114,"line":1507},[112,2352,2353],{"class":129},"        children: [\n",[112,2355,2356,2359,2361],{"class":114,"line":1512},[112,2357,2358],{"class":129},"          { type: ",[112,2360,2316],{"class":136},[112,2362,2363],{"class":129},", props: { children: post.data.title } },\n",[112,2365,2366],{"class":114,"line":1518},[112,2367,2368],{"class":129},"        ],\n",[112,2370,2371],{"class":114,"line":1524},[112,2372,2373],{"class":129},"      },\n",[112,2375,2376],{"class":114,"line":1530},[112,2377,2378],{"class":129},"    },\n",[112,2380,2381,2384,2387,2390,2393],{"class":114,"line":1536},[112,2382,2383],{"class":129},"    { width: ",[112,2385,2386],{"class":156},"1200",[112,2388,2389],{"class":129},", height: ",[112,2391,2392],{"class":156},"630",[112,2394,2395],{"class":129}," }\n",[112,2397,2398],{"class":114,"line":1542},[112,2399,2039],{"class":129},[112,2401,2403],{"class":114,"line":2402},25,[112,2404,147],{"emptyLinePlaceholder":146},[112,2406,2408],{"class":114,"line":2407},26,[112,2409,2410],{"class":118},"  \u002F\u002F SharpでSVGをPNGに変換\n",[112,2412,2414,2416,2419,2421,2423,2426,2429,2431,2434,2437,2439,2442],{"class":114,"line":2413},27,[112,2415,1974],{"class":125},[112,2417,2418],{"class":156}," png",[112,2420,160],{"class":125},[112,2422,419],{"class":125},[112,2424,2425],{"class":163}," sharp",[112,2427,2428],{"class":129},"(Buffer.",[112,2430,133],{"class":163},[112,2432,2433],{"class":129},"(svg)).",[112,2435,2436],{"class":163},"png",[112,2438,213],{"class":129},[112,2440,2441],{"class":163},"toBuffer",[112,2443,462],{"class":129},[112,2445,2447],{"class":114,"line":2446},28,[112,2448,147],{"emptyLinePlaceholder":146},[112,2450,2452,2454,2456,2459],{"class":114,"line":2451},29,[112,2453,2048],{"class":125},[112,2455,232],{"class":125},[112,2457,2458],{"class":163}," Response",[112,2460,2461],{"class":129},"(png, {\n",[112,2463,2465,2468,2471,2473,2476],{"class":114,"line":2464},30,[112,2466,2467],{"class":129},"    headers: { ",[112,2469,2470],{"class":136},"'Content-Type'",[112,2472,567],{"class":129},[112,2474,2475],{"class":136},"'image\u002Fpng'",[112,2477,2478],{"class":129}," },\n",[112,2480,2482],{"class":114,"line":2481},31,[112,2483,1495],{"class":129},[112,2485,2487],{"class":114,"line":2486},32,[112,2488,1305],{"class":129},[15,2490,2491],{},"記事タイトル、日付、サイト名が含まれたカード型のOGP画像が自動生成され、SNSでのシェア時に美しく表示されます。",[11,2493,1061],{"id":1061},[15,2495,2496,2497,2499],{},"アイコンには ",[90,2498,1061],{}," を使用しています。Iconifyの全アイコンセットにアクセスでき、使用するアイコンだけがバンドルされます。",[103,2501,2503],{"className":1419,"code":2502,"language":1421,"meta":108,"style":108},"---\nimport { Icon } from 'astro-icon\u002Fcomponents';\n---\n\n\u003CIcon name=\"lucide:calendar\" class=\"size-4\" \u002F>\n\u003CIcon name=\"lucide:clock\" class=\"size-4\" \u002F>\n",[90,2504,2505,2509,2514,2518,2522,2527],{"__ignoreMap":108},[112,2506,2507],{"class":114,"line":115},[112,2508,1428],{},[112,2510,2511],{"class":114,"line":122},[112,2512,2513],{},"import { Icon } from 'astro-icon\u002Fcomponents';\n",[112,2515,2516],{"class":114,"line":143},[112,2517,1428],{},[112,2519,2520],{"class":114,"line":150},[112,2521,147],{"emptyLinePlaceholder":146},[112,2523,2524],{"class":114,"line":170},[112,2525,2526],{},"\u003CIcon name=\"lucide:calendar\" class=\"size-4\" \u002F>\n",[112,2528,2529],{"class":114,"line":182},[112,2530,2531],{},"\u003CIcon name=\"lucide:clock\" class=\"size-4\" \u002F>\n",[15,2533,2534],{},"設定でlucideアイコンセットを指定：",[103,2536,2538],{"className":105,"code":2537,"language":107,"meta":108,"style":108},"\u002F\u002F astro.config.mjs\nexport default defineConfig({\n  integrations: [\n    icon({\n      include: {\n        lucide: ['*'],\n      },\n    }),\n  ],\n});\n",[90,2539,2540,2544,2554,2558,2565,2570,2580,2584,2588,2592],{"__ignoreMap":108},[112,2541,2542],{"class":114,"line":115},[112,2543,1850],{"class":118},[112,2545,2546,2548,2550,2552],{"class":114,"line":122},[112,2547,288],{"class":125},[112,2549,291],{"class":125},[112,2551,686],{"class":163},[112,2553,167],{"class":129},[112,2555,2556],{"class":114,"line":143},[112,2557,1875],{"class":129},[112,2559,2560,2563],{"class":114,"line":150},[112,2561,2562],{"class":163},"    icon",[112,2564,167],{"class":129},[112,2566,2567],{"class":114,"line":170},[112,2568,2569],{"class":129},"      include: {\n",[112,2571,2572,2575,2578],{"class":114,"line":182},[112,2573,2574],{"class":129},"        lucide: [",[112,2576,2577],{"class":136},"'*'",[112,2579,746],{"class":129},[112,2581,2582],{"class":114,"line":193},[112,2583,2373],{"class":129},[112,2585,2586],{"class":114,"line":205},[112,2587,370],{"class":129},[112,2589,2590],{"class":114,"line":241},[112,2591,1887],{"class":129},[112,2593,2594],{"class":114,"line":247},[112,2595,250],{"class":129},[11,2597,2598],{"id":2598},"デザインシステム",[15,2600,2601,2602,2605],{},"このブログは",[27,2603,2604],{},"モダン・ミニマリズム + ビビッドアクセント","のコンセプトでデザインされています。",[83,2607,2608],{"id":2608},"主な特徴",[31,2610,2611,2617,2623,2629,2635],{},[34,2612,2613,2616],{},[27,2614,2615],{},"カードグリッドレイアウト"," - 2カラムで整然と配置されたブログ一覧",[34,2618,2619,2622],{},[27,2620,2621],{},"グラデーション多用"," - 紫→ピンク系のモダンなグラデーション",[34,2624,2625,2628],{},[27,2626,2627],{},"マイクロインタラクション"," - ホバー時の浮き上がり効果、スムーズなトランジション",[34,2630,2631,2634],{},[27,2632,2633],{},"読みやすさ重視"," - 広い行間（2.0）、適切なフォントサイズ",[34,2636,2637,2640],{},[27,2638,2639],{},"ダークモード完全対応"," - システム設定に応じた自動切り替え",[11,2642,2644],{"id":2643},"文章品質管理-textlint","文章品質管理 - textlint",[15,2646,2647,2648,2652],{},"ブログ記事の品質を保つため、",[759,2649,1079],{"href":2650,"rel":2651},"https:\u002F\u002Ftextlint.github.io\u002F",[763]," を導入しています。",[15,2654,2655,2658],{},[90,2656,2657],{},"@textlint-ja\u002Ftextlint-rule-preset-ai-writing"," プリセットを使用することで、以下をチェックできます",[31,2660,2661,2667,2673,2679],{},[34,2662,2663,2666],{},[27,2664,2665],{},"重複表現"," - 同じ言葉の繰り返しを検出",[34,2668,2669,2672],{},[27,2670,2671],{},"冗長な表現"," - 不要な言い回しを指摘",[34,2674,2675,2678],{},[27,2676,2677],{},"ら抜き言葉"," - 文法的な誤りをチェック",[34,2680,2681,2684],{},[27,2682,2683],{},"表記ゆれ"," - 表記の統一性を確認",[103,2686,2688],{"className":771,"code":2687,"language":773,"meta":108,"style":108},"# 記事をチェック\npnpm lint:text\n\n# 自動修正\npnpm lint:text:fix\n",[90,2689,2690,2695,2703,2707,2712],{"__ignoreMap":108},[112,2691,2692],{"class":114,"line":115},[112,2693,2694],{"class":118},"# 記事をチェック\n",[112,2696,2697,2700],{"class":114,"line":122},[112,2698,2699],{"class":163},"pnpm",[112,2701,2702],{"class":136}," lint:text\n",[112,2704,2705],{"class":114,"line":143},[112,2706,147],{"emptyLinePlaceholder":146},[112,2708,2709],{"class":114,"line":150},[112,2710,2711],{"class":118},"# 自動修正\n",[112,2713,2714,2716],{"class":114,"line":170},[112,2715,2699],{"class":163},[112,2717,2718],{"class":136}," lint:text:fix\n",[15,2720,2721],{},"AI技術を活用したルールセットにより、より自然で読みやすい文章を書けるようになります。",[11,2723,2724],{"id":2724},"パフォーマンス",[15,2726,2727],{},"Astroの強みを活かし、超高速なブログを実現しています",[31,2729,2730,2736,2742,2748],{},[34,2731,2732,2735],{},[27,2733,2734],{},"ゼロJavaScript"," - デフォルトでクライアントサイドJSなし",[34,2737,2738,2741],{},[27,2739,2740],{},"静的サイト生成"," - ビルド時に全ページを事前生成",[34,2743,2744,2747],{},[27,2745,2746],{},"最適化された画像"," - 自動的にWebP\u002FAVIF変換",[34,2749,2750,2752],{},[27,2751,1036],{}," - PostCSS不要でビルドが高速",[11,2754,2756],{"id":2755},"デプロイ-cloudflare-workers","デプロイ - Cloudflare Workers",[15,2758,2759,2760,2764],{},"このブログは ",[759,2761,76],{"href":2762,"rel":2763},"https:\u002F\u002Fworkers.cloudflare.com\u002F",[763]," でホストされています。",[15,2766,2767],{},"Cloudflare Workersを選んだ理由は以下のとおりです",[31,2769,2770,2776,2782,2788],{},[34,2771,2772,2775],{},[27,2773,2774],{},"エッジでの実行"," - 世界中のエッジロケーションでコードを実行",[34,2777,2778,2781],{},[27,2779,2780],{},"超高速なレスポンス"," - エッジからの直接配信で低レイテンシ",[34,2783,2784,2787],{},[27,2785,2786],{},"無料で使いやすい"," - 個人ブログには十分な無料枠",[34,2789,2790,2792],{},[27,2791,1134],{}," - 静的サイトだけでなく、動的な処理も可能",[83,2794,2795],{"id":2795},"wranglerでデプロイ",[15,2797,2798,2802],{},[759,2799,1091],{"href":2800,"rel":2801},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fwrangler\u002F",[763]," を使用することで、コマンド一つでデプロイできます。",[103,2804,2806],{"className":771,"code":2805,"language":773,"meta":108,"style":108},"# ビルド＆デプロイ\npnpm build && pnpm deploy\n",[90,2807,2808,2813],{"__ignoreMap":108},[112,2809,2810],{"class":114,"line":115},[112,2811,2812],{"class":118},"# ビルド＆デプロイ\n",[112,2814,2815,2817,2820,2823,2825],{"class":114,"line":122},[112,2816,2699],{"class":163},[112,2818,2819],{"class":136}," build",[112,2821,2822],{"class":129}," && ",[112,2824,2699],{"class":163},[112,2826,2827],{"class":136}," deploy\n",[15,2829,2830,2833],{},[90,2831,2832],{},"wrangler.toml"," で設定を管理し、CI\u002FCDパイプラインにも簡単に組み込めます。",[11,2835,919],{"id":919},[15,2837,2838],{},"Astro 5 と Tailwind CSS v4 の組み合わせにより、超高速でモダンなブログを構築できました。",[15,2840,2841],{},"主な特徴をまとめると：",[31,2843,2844,2849,2855,2861],{},[34,2845,2846,2848],{},[27,2847,1018],{}," - 型安全な記事管理",[34,2850,2851,2854],{},[27,2852,2853],{},"動的OGP画像生成"," - satoriとsharpによる美しいOGP画像",[34,2856,2857,2860],{},[27,2858,2859],{},"RSSフィード"," - 購読者のための標準サポート",[34,2862,2863,2866],{},[27,2864,2865],{},"モダンなデザインシステム"," - ダークモード完全対応",[15,2868,2869],{},"これらの技術により、快適な執筆・閲覧体験を実現しています。",[15,2871,2872],{},"今後もこのブログを通じて、技術的な知見や経験を発信していきます！",[944,2874,2875],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":108,"searchDepth":122,"depth":122,"links":2877},[2878,2879,2880,2884,2885,2889,2890,2891,2892,2893,2896,2897,2898,2901],{"id":975,"depth":122,"text":975},{"id":984,"depth":122,"text":984},{"id":1095,"depth":122,"text":1096,"children":2881},[2882,2883],{"id":1144,"depth":143,"text":1018},{"id":1311,"depth":143,"text":1311},{"id":1412,"depth":122,"text":1413},{"id":1551,"depth":122,"text":1036,"children":2886},[2887,2888],{"id":1584,"depth":143,"text":1584},{"id":1795,"depth":143,"text":1042},{"id":1833,"depth":122,"text":1834},{"id":1897,"depth":122,"text":1898},{"id":2160,"depth":122,"text":2161},{"id":1061,"depth":122,"text":1061},{"id":2598,"depth":122,"text":2598,"children":2894},[2895],{"id":2608,"depth":143,"text":2608},{"id":2643,"depth":122,"text":2644},{"id":2724,"depth":122,"text":2724},{"id":2755,"depth":122,"text":2756,"children":2899},[2900],{"id":2795,"depth":143,"text":2795},{"id":919,"depth":122,"text":919},"2026-02-13","Astro 5、Tailwind CSS v4、Content Collectionsなど、このブログを構築している技術スタックについて紹介します。",{"tags":2905},[1421,107,2906,2907],"blog","tech-stack","\u002Fblog\u002Fblog-tech-stack",{"title":970,"description":2903},"blog\u002Fblog-tech-stack","TiHWr01R-YzuvxGKVd1IX9BYdfWB-hvHp4wxgot6Pc4",{"id":2913,"title":2914,"body":2915,"date":5985,"description":5986,"extension":962,"meta":5987,"navigation":146,"path":5991,"seo":5992,"stem":5993,"__hash__":5994},"blog\u002Fblog\u002Flinear-clipper-extension.md","Linear Clipper 拡張機能を作ってみた",{"type":8,"value":2916,"toc":5965},[2917,2919,2928,2931,2947,2951,2954,3238,3242,3262,3265,3289,3293,3296,4104,4107,4138,4142,4145,4753,4756,4785,4789,4792,5131,5134,5155,5159,5162,5943,5946,5954,5957,5959,5962],[11,2918,13],{"id":13},[15,2920,2921,2922,2927],{},"タスク管理ツールとして ",[759,2923,2926],{"href":2924,"rel":2925},"https:\u002F\u002Flinear.app",[763],"Linear"," を使っているのですが、ブラウジング中に見つけた記事やドキュメントをイシューに紐付けたいことがよくあります。毎回 Linear を開いてURLをコピペするのは面倒なので、Chrome 拡張機能を作ってみました。",[11,2929,2930],{"id":2930},"参考リンク",[31,2932,2933,2940],{},[34,2934,2935],{},[759,2936,2939],{"href":2937,"rel":2938},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fextensions\u002Freference\u002Fmanifest?hl=ja#register-a-content-script",[763],"マニフェスト ファイル形式 | Manifest | Chrome for Developers",[34,2941,2942],{},[759,2943,2946],{"href":2944,"rel":2945},"https:\u002F\u002Fstudio.apollographql.com\u002Fpublic\u002FLinear-API\u002Fvariant\u002Fcurrent\u002Fschema\u002Freference\u002Fobjects\u002FAttachmentPayload",[763],"Schema | Linear API@current | Studio",[11,2948,2950],{"id":2949},"manifestjson","manifest.json",[15,2952,2953],{},"Chrome 拡張機能のメタデータと設定を定義する JSON ファイルです。拡張機能のルートディレクトリに必須です。",[103,2955,2959],{"className":2956,"code":2957,"language":2958,"meta":108,"style":108},"language-json shiki shiki-themes github-light github-dark","{\n  \"manifest_version\": 3,\n  \"name\": \"Linear Clipper\",\n  \"version\": \"1.0.0\",\n  \"description\": \"現在のページURLをLinearイシューにクリップする\",\n  \"permissions\": [\n    \"storage\",\n    \"activeTab\"\n  ],\n  \"host_permissions\": [\n    \"https:\u002F\u002Fapi.linear.app\u002F*\"\n  ],\n  \"action\": {\n    \"default_popup\": \"popup\u002Fpopup.html\",\n    \"default_icon\": {\n      \"16\": \"icons\u002Ficon16.png\",\n      \"48\": \"icons\u002Ficon48.png\",\n      \"128\": \"icons\u002Ficon128.png\"\n    }\n  },\n  \"options_ui\": {\n    \"page\": \"options\u002Foptions.html\",\n    \"open_in_tab\": true\n  },\n  \"background\": {\n    \"service_worker\": \"scripts\u002Fbackground.js\",\n    \"type\": \"module\"\n  },\n  \"icons\": {\n    \"16\": \"icons\u002Ficon16.png\",\n    \"48\": \"icons\u002Ficon48.png\",\n    \"128\": \"icons\u002Ficon128.png\"\n  }\n}\n","json",[90,2960,2961,2966,2978,2990,3002,3014,3022,3029,3034,3038,3045,3050,3054,3061,3073,3080,3092,3104,3114,3119,3123,3130,3142,3152,3156,3163,3175,3185,3189,3196,3207,3218,3227,3233],{"__ignoreMap":108},[112,2962,2963],{"class":114,"line":115},[112,2964,2965],{"class":129},"{\n",[112,2967,2968,2971,2973,2976],{"class":114,"line":122},[112,2969,2970],{"class":156},"  \"manifest_version\"",[112,2972,567],{"class":129},[112,2974,2975],{"class":156},"3",[112,2977,179],{"class":129},[112,2979,2980,2983,2985,2988],{"class":114,"line":143},[112,2981,2982],{"class":156},"  \"name\"",[112,2984,567],{"class":129},[112,2986,2987],{"class":136},"\"Linear Clipper\"",[112,2989,179],{"class":129},[112,2991,2992,2995,2997,3000],{"class":114,"line":150},[112,2993,2994],{"class":156},"  \"version\"",[112,2996,567],{"class":129},[112,2998,2999],{"class":136},"\"1.0.0\"",[112,3001,179],{"class":129},[112,3003,3004,3007,3009,3012],{"class":114,"line":170},[112,3005,3006],{"class":156},"  \"description\"",[112,3008,567],{"class":129},[112,3010,3011],{"class":136},"\"現在のページURLをLinearイシューにクリップする\"",[112,3013,179],{"class":129},[112,3015,3016,3019],{"class":114,"line":182},[112,3017,3018],{"class":156},"  \"permissions\"",[112,3020,3021],{"class":129},": [\n",[112,3023,3024,3027],{"class":114,"line":193},[112,3025,3026],{"class":136},"    \"storage\"",[112,3028,179],{"class":129},[112,3030,3031],{"class":114,"line":205},[112,3032,3033],{"class":136},"    \"activeTab\"\n",[112,3035,3036],{"class":114,"line":241},[112,3037,1887],{"class":129},[112,3039,3040,3043],{"class":114,"line":247},[112,3041,3042],{"class":156},"  \"host_permissions\"",[112,3044,3021],{"class":129},[112,3046,3047],{"class":114,"line":351},[112,3048,3049],{"class":136},"    \"https:\u002F\u002Fapi.linear.app\u002F*\"\n",[112,3051,3052],{"class":114,"line":361},[112,3053,1887],{"class":129},[112,3055,3056,3059],{"class":114,"line":367},[112,3057,3058],{"class":156},"  \"action\"",[112,3060,852],{"class":129},[112,3062,3063,3066,3068,3071],{"class":114,"line":373},[112,3064,3065],{"class":156},"    \"default_popup\"",[112,3067,567],{"class":129},[112,3069,3070],{"class":136},"\"popup\u002Fpopup.html\"",[112,3072,179],{"class":129},[112,3074,3075,3078],{"class":114,"line":379},[112,3076,3077],{"class":156},"    \"default_icon\"",[112,3079,852],{"class":129},[112,3081,3082,3085,3087,3090],{"class":114,"line":1302},[112,3083,3084],{"class":156},"      \"16\"",[112,3086,567],{"class":129},[112,3088,3089],{"class":136},"\"icons\u002Ficon16.png\"",[112,3091,179],{"class":129},[112,3093,3094,3097,3099,3102],{"class":114,"line":1502},[112,3095,3096],{"class":156},"      \"48\"",[112,3098,567],{"class":129},[112,3100,3101],{"class":136},"\"icons\u002Ficon48.png\"",[112,3103,179],{"class":129},[112,3105,3106,3109,3111],{"class":114,"line":1507},[112,3107,3108],{"class":156},"      \"128\"",[112,3110,567],{"class":129},[112,3112,3113],{"class":136},"\"icons\u002Ficon128.png\"\n",[112,3115,3116],{"class":114,"line":1512},[112,3117,3118],{"class":129},"    }\n",[112,3120,3121],{"class":114,"line":1518},[112,3122,376],{"class":129},[112,3124,3125,3128],{"class":114,"line":1524},[112,3126,3127],{"class":156},"  \"options_ui\"",[112,3129,852],{"class":129},[112,3131,3132,3135,3137,3140],{"class":114,"line":1530},[112,3133,3134],{"class":156},"    \"page\"",[112,3136,567],{"class":129},[112,3138,3139],{"class":136},"\"options\u002Foptions.html\"",[112,3141,179],{"class":129},[112,3143,3144,3147,3149],{"class":114,"line":1536},[112,3145,3146],{"class":156},"    \"open_in_tab\"",[112,3148,567],{"class":129},[112,3150,3151],{"class":156},"true\n",[112,3153,3154],{"class":114,"line":1542},[112,3155,376],{"class":129},[112,3157,3158,3161],{"class":114,"line":2402},[112,3159,3160],{"class":156},"  \"background\"",[112,3162,852],{"class":129},[112,3164,3165,3168,3170,3173],{"class":114,"line":2407},[112,3166,3167],{"class":156},"    \"service_worker\"",[112,3169,567],{"class":129},[112,3171,3172],{"class":136},"\"scripts\u002Fbackground.js\"",[112,3174,179],{"class":129},[112,3176,3177,3180,3182],{"class":114,"line":2413},[112,3178,3179],{"class":156},"    \"type\"",[112,3181,567],{"class":129},[112,3183,3184],{"class":136},"\"module\"\n",[112,3186,3187],{"class":114,"line":2446},[112,3188,376],{"class":129},[112,3190,3191,3194],{"class":114,"line":2451},[112,3192,3193],{"class":156},"  \"icons\"",[112,3195,852],{"class":129},[112,3197,3198,3201,3203,3205],{"class":114,"line":2464},[112,3199,3200],{"class":156},"    \"16\"",[112,3202,567],{"class":129},[112,3204,3089],{"class":136},[112,3206,179],{"class":129},[112,3208,3209,3212,3214,3216],{"class":114,"line":2481},[112,3210,3211],{"class":156},"    \"48\"",[112,3213,567],{"class":129},[112,3215,3101],{"class":136},[112,3217,179],{"class":129},[112,3219,3220,3223,3225],{"class":114,"line":2486},[112,3221,3222],{"class":156},"    \"128\"",[112,3224,567],{"class":129},[112,3226,3113],{"class":136},[112,3228,3230],{"class":114,"line":3229},33,[112,3231,3232],{"class":129},"  }\n",[112,3234,3236],{"class":114,"line":3235},34,[112,3237,1452],{"class":129},[83,3239,3241],{"id":3240},"permissions-と-host_permissions-の説明","permissions と host_permissions の説明",[31,3243,3244,3250,3256],{},[34,3245,3246,3249],{},[90,3247,3248],{},"\"storage\"",": API キーやチーム ID を拡張機能のストレージに保存するために必要",[34,3251,3252,3255],{},[90,3253,3254],{},"\"activeTab\"",": 現在のタブの URL とタイトルを取得するために必要",[34,3257,3258,3261],{},[90,3259,3260],{},"\"host_permissions\": [\"https:\u002F\u002Fapi.linear.app\u002F*\"]",": Linear API への fetch を許可し、CORS 制限を回避する",[83,3263,3264],{"id":3264},"各設定項目",[31,3266,3267,3273,3283],{},[34,3268,3269,3272],{},[90,3270,3271],{},"\"default_popup\"",": 拡張機能のアイコンをクリックしたときに表示されるポップアップの HTML",[34,3274,3275,3278,3279,3282],{},[90,3276,3277],{},"\"options_ui\"",": 拡張機能のオプションページ。",[90,3280,3281],{},"open_in_tab: true"," で新しいタブで開く",[34,3284,3285,3288],{},[90,3286,3287],{},"\"background\"",": Service Worker として動作するバックグラウンドスクリプト。アイドル時は自動停止する",[11,3290,3292],{"id":3291},"optionsoptionsjs","options\u002Foptions.js",[15,3294,3295],{},"拡張機能のオプションページです。API キーとチーム ID を設定できるようにします。",[103,3297,3299],{"className":1909,"code":3298,"language":1911,"meta":108,"style":108},"async function init() {\n  \u002F\u002F 既存の設定を読み込む\n  const { apiKey, teamId } = await chrome.storage.sync.get(['apiKey', 'teamId']);\n\n  if (apiKey) {\n    apiKeyInput.value = apiKey;\n    await loadTeams(apiKey, teamId);\n  }\n}\n\nasync function loadTeams(apiKey, selectedTeamId = null) {\n  teamSelect.disabled = true;\n  teamSelect.innerHTML = '\u003Coption value=\"\">読み込み中...\u003C\u002Foption>';\n\n  try {\n    \u002F\u002F background.jsにメッセージを送信してチーム一覧を取得\n    const response = await chrome.runtime.sendMessage({\n      action: 'fetchTeams',\n    });\n\n    if (response.error) {\n      throw new Error(response.error);\n    }\n\n    teamSelect.innerHTML = '\u003Coption value=\"\">-- チームを選択 --\u003C\u002Foption>';\n\n    response.teams.forEach((team) => {\n      const option = document.createElement('option');\n      option.value = team.id;\n      option.textContent = team.name;\n      if (team.id === selectedTeamId) {\n        option.selected = true;\n      }\n      teamSelect.appendChild(option);\n    });\n\n    teamSelect.disabled = false;\n  } catch (error) {\n    teamSelect.innerHTML = '\u003Coption value=\"\">エラー: ' + error.message + '\u003C\u002Foption>';\n    showStatus('チームの取得に失敗しました: ' + error.message, 'error');\n  }\n}\n\n\u002F\u002F debounce: 入力のたびにAPIを叩かないよう500ms待機\napiKeyInput.addEventListener('input', () => {\n  clearTimeout(debounceTimer);\n  debounceTimer = setTimeout(async () => {\n    const apiKey = apiKeyInput.value.trim();\n    if (apiKey) {\n      await chrome.storage.sync.set({ apiKey });\n      await loadTeams(apiKey);\n    }\n  }, 500);\n});\n\nsaveBtn.addEventListener('click', async () => {\n  const apiKey = apiKeyInput.value.trim();\n  const teamId = teamSelect.value;\n\n  if (!apiKey) {\n    showStatus('APIキーを入力してください', 'error');\n    return;\n  }\n\n  if (!teamId) {\n    showStatus('チームを選択してください', 'error');\n    return;\n  }\n\n  try {\n    await chrome.storage.sync.set({ apiKey, teamId });\n    showStatus('設定を保存しました', 'success');\n  } catch (error) {\n    showStatus('保存に失敗しました: ' + error.message, 'error');\n  }\n});\n",[90,3300,3301,3314,3319,3359,3363,3371,3381,3392,3396,3400,3404,3428,3440,3452,3456,3463,3468,3488,3498,3503,3507,3515,3528,3532,3536,3548,3552,3571,3594,3604,3614,3628,3639,3644,3655,3660,3665,3678,3690,3714,3735,3740,3745,3750,3756,3776,3785,3807,3825,3832,3846,3856,3861,3872,3877,3882,3905,3920,3933,3938,3952,3968,3976,3981,3986,3998,4014,4021,4026,4031,4038,4050,4067,4076,4094,4099],{"__ignoreMap":108},[112,3302,3303,3306,3308,3311],{"class":114,"line":115},[112,3304,3305],{"class":125},"async",[112,3307,1958],{"class":125},[112,3309,3310],{"class":163}," init",[112,3312,3313],{"class":129},"() {\n",[112,3315,3316],{"class":114,"line":122},[112,3317,3318],{"class":118},"  \u002F\u002F 既存の設定を読み込む\n",[112,3320,3321,3323,3325,3328,3330,3333,3335,3337,3339,3342,3345,3348,3351,3353,3356],{"class":114,"line":143},[112,3322,1974],{"class":125},[112,3324,561],{"class":129},[112,3326,3327],{"class":156},"apiKey",[112,3329,447],{"class":129},[112,3331,3332],{"class":156},"teamId",[112,3334,573],{"class":129},[112,3336,576],{"class":125},[112,3338,419],{"class":125},[112,3340,3341],{"class":129}," chrome.storage.sync.",[112,3343,3344],{"class":163},"get",[112,3346,3347],{"class":129},"([",[112,3349,3350],{"class":136},"'apiKey'",[112,3352,447],{"class":129},[112,3354,3355],{"class":136},"'teamId'",[112,3357,3358],{"class":129},"]);\n",[112,3360,3361],{"class":114,"line":150},[112,3362,147],{"emptyLinePlaceholder":146},[112,3364,3365,3368],{"class":114,"line":170},[112,3366,3367],{"class":125},"  if",[112,3369,3370],{"class":129}," (apiKey) {\n",[112,3372,3373,3376,3378],{"class":114,"line":182},[112,3374,3375],{"class":129},"    apiKeyInput.value ",[112,3377,576],{"class":125},[112,3379,3380],{"class":129}," apiKey;\n",[112,3382,3383,3386,3389],{"class":114,"line":193},[112,3384,3385],{"class":125},"    await",[112,3387,3388],{"class":163}," loadTeams",[112,3390,3391],{"class":129},"(apiKey, teamId);\n",[112,3393,3394],{"class":114,"line":205},[112,3395,3232],{"class":129},[112,3397,3398],{"class":114,"line":241},[112,3399,1452],{"class":129},[112,3401,3402],{"class":114,"line":247},[112,3403,147],{"emptyLinePlaceholder":146},[112,3405,3406,3408,3410,3412,3414,3416,3418,3421,3423,3426],{"class":114,"line":351},[112,3407,3305],{"class":125},[112,3409,1958],{"class":125},[112,3411,3388],{"class":163},[112,3413,425],{"class":129},[112,3415,3327],{"class":222},[112,3417,447],{"class":129},[112,3419,3420],{"class":222},"selectedTeamId",[112,3422,160],{"class":125},[112,3424,3425],{"class":156}," null",[112,3427,1969],{"class":129},[112,3429,3430,3433,3435,3438],{"class":114,"line":361},[112,3431,3432],{"class":129},"  teamSelect.disabled ",[112,3434,576],{"class":125},[112,3436,3437],{"class":156}," true",[112,3439,140],{"class":129},[112,3441,3442,3445,3447,3450],{"class":114,"line":367},[112,3443,3444],{"class":129},"  teamSelect.innerHTML ",[112,3446,576],{"class":125},[112,3448,3449],{"class":136}," '\u003Coption value=\"\">読み込み中...\u003C\u002Foption>'",[112,3451,140],{"class":129},[112,3453,3454],{"class":114,"line":373},[112,3455,147],{"emptyLinePlaceholder":146},[112,3457,3458,3461],{"class":114,"line":379},[112,3459,3460],{"class":125},"  try",[112,3462,1294],{"class":129},[112,3464,3465],{"class":114,"line":1302},[112,3466,3467],{"class":118},"    \u002F\u002F background.jsにメッセージを送信してチーム一覧を取得\n",[112,3469,3470,3473,3476,3478,3480,3483,3486],{"class":114,"line":1502},[112,3471,3472],{"class":125},"    const",[112,3474,3475],{"class":156}," response",[112,3477,160],{"class":125},[112,3479,419],{"class":125},[112,3481,3482],{"class":129}," chrome.runtime.",[112,3484,3485],{"class":163},"sendMessage",[112,3487,167],{"class":129},[112,3489,3490,3493,3496],{"class":114,"line":1507},[112,3491,3492],{"class":129},"      action: ",[112,3494,3495],{"class":136},"'fetchTeams'",[112,3497,179],{"class":129},[112,3499,3500],{"class":114,"line":1512},[112,3501,3502],{"class":129},"    });\n",[112,3504,3505],{"class":114,"line":1518},[112,3506,147],{"emptyLinePlaceholder":146},[112,3508,3509,3512],{"class":114,"line":1524},[112,3510,3511],{"class":125},"    if",[112,3513,3514],{"class":129}," (response.error) {\n",[112,3516,3517,3520,3522,3525],{"class":114,"line":1530},[112,3518,3519],{"class":125},"      throw",[112,3521,232],{"class":125},[112,3523,3524],{"class":163}," Error",[112,3526,3527],{"class":129},"(response.error);\n",[112,3529,3530],{"class":114,"line":1536},[112,3531,3118],{"class":129},[112,3533,3534],{"class":114,"line":1542},[112,3535,147],{"emptyLinePlaceholder":146},[112,3537,3538,3541,3543,3546],{"class":114,"line":2402},[112,3539,3540],{"class":129},"    teamSelect.innerHTML ",[112,3542,576],{"class":125},[112,3544,3545],{"class":136}," '\u003Coption value=\"\">-- チームを選択 --\u003C\u002Foption>'",[112,3547,140],{"class":129},[112,3549,3550],{"class":114,"line":2407},[112,3551,147],{"emptyLinePlaceholder":146},[112,3553,3554,3557,3560,3562,3565,3567,3569],{"class":114,"line":2413},[112,3555,3556],{"class":129},"    response.teams.",[112,3558,3559],{"class":163},"forEach",[112,3561,219],{"class":129},[112,3563,3564],{"class":222},"team",[112,3566,226],{"class":129},[112,3568,229],{"class":125},[112,3570,1294],{"class":129},[112,3572,3573,3576,3579,3581,3584,3587,3589,3592],{"class":114,"line":2446},[112,3574,3575],{"class":125},"      const",[112,3577,3578],{"class":156}," option",[112,3580,160],{"class":125},[112,3582,3583],{"class":129}," document.",[112,3585,3586],{"class":163},"createElement",[112,3588,425],{"class":129},[112,3590,3591],{"class":136},"'option'",[112,3593,635],{"class":129},[112,3595,3596,3599,3601],{"class":114,"line":2451},[112,3597,3598],{"class":129},"      option.value ",[112,3600,576],{"class":125},[112,3602,3603],{"class":129}," team.id;\n",[112,3605,3606,3609,3611],{"class":114,"line":2464},[112,3607,3608],{"class":129},"      option.textContent ",[112,3610,576],{"class":125},[112,3612,3613],{"class":129}," team.name;\n",[112,3615,3616,3619,3622,3625],{"class":114,"line":2481},[112,3617,3618],{"class":125},"      if",[112,3620,3621],{"class":129}," (team.id ",[112,3623,3624],{"class":125},"===",[112,3626,3627],{"class":129}," selectedTeamId) {\n",[112,3629,3630,3633,3635,3637],{"class":114,"line":2486},[112,3631,3632],{"class":129},"        option.selected ",[112,3634,576],{"class":125},[112,3636,3437],{"class":156},[112,3638,140],{"class":129},[112,3640,3641],{"class":114,"line":3229},[112,3642,3643],{"class":129},"      }\n",[112,3645,3646,3649,3652],{"class":114,"line":3235},[112,3647,3648],{"class":129},"      teamSelect.",[112,3650,3651],{"class":163},"appendChild",[112,3653,3654],{"class":129},"(option);\n",[112,3656,3658],{"class":114,"line":3657},35,[112,3659,3502],{"class":129},[112,3661,3663],{"class":114,"line":3662},36,[112,3664,147],{"emptyLinePlaceholder":146},[112,3666,3668,3671,3673,3676],{"class":114,"line":3667},37,[112,3669,3670],{"class":129},"    teamSelect.disabled ",[112,3672,576],{"class":125},[112,3674,3675],{"class":156}," false",[112,3677,140],{"class":129},[112,3679,3681,3684,3687],{"class":114,"line":3680},38,[112,3682,3683],{"class":129},"  } ",[112,3685,3686],{"class":125},"catch",[112,3688,3689],{"class":129}," (error) {\n",[112,3691,3693,3695,3697,3700,3703,3706,3709,3712],{"class":114,"line":3692},39,[112,3694,3540],{"class":129},[112,3696,576],{"class":125},[112,3698,3699],{"class":136}," '\u003Coption value=\"\">エラー: '",[112,3701,3702],{"class":125}," +",[112,3704,3705],{"class":129}," error.message ",[112,3707,3708],{"class":125},"+",[112,3710,3711],{"class":136}," '\u003C\u002Foption>'",[112,3713,140],{"class":129},[112,3715,3717,3720,3722,3725,3727,3730,3733],{"class":114,"line":3716},40,[112,3718,3719],{"class":163},"    showStatus",[112,3721,425],{"class":129},[112,3723,3724],{"class":136},"'チームの取得に失敗しました: '",[112,3726,3702],{"class":125},[112,3728,3729],{"class":129}," error.message, ",[112,3731,3732],{"class":136},"'error'",[112,3734,635],{"class":129},[112,3736,3738],{"class":114,"line":3737},41,[112,3739,3232],{"class":129},[112,3741,3743],{"class":114,"line":3742},42,[112,3744,1452],{"class":129},[112,3746,3748],{"class":114,"line":3747},43,[112,3749,147],{"emptyLinePlaceholder":146},[112,3751,3753],{"class":114,"line":3752},44,[112,3754,3755],{"class":118},"\u002F\u002F debounce: 入力のたびにAPIを叩かないよう500ms待機\n",[112,3757,3759,3762,3765,3767,3770,3772,3774],{"class":114,"line":3758},45,[112,3760,3761],{"class":129},"apiKeyInput.",[112,3763,3764],{"class":163},"addEventListener",[112,3766,425],{"class":129},[112,3768,3769],{"class":136},"'input'",[112,3771,595],{"class":129},[112,3773,229],{"class":125},[112,3775,1294],{"class":129},[112,3777,3779,3782],{"class":114,"line":3778},46,[112,3780,3781],{"class":163},"  clearTimeout",[112,3783,3784],{"class":129},"(debounceTimer);\n",[112,3786,3788,3791,3793,3796,3798,3800,3803,3805],{"class":114,"line":3787},47,[112,3789,3790],{"class":129},"  debounceTimer ",[112,3792,576],{"class":125},[112,3794,3795],{"class":163}," setTimeout",[112,3797,425],{"class":129},[112,3799,3305],{"class":125},[112,3801,3802],{"class":129}," () ",[112,3804,229],{"class":125},[112,3806,1294],{"class":129},[112,3808,3810,3812,3815,3817,3820,3823],{"class":114,"line":3809},48,[112,3811,3472],{"class":125},[112,3813,3814],{"class":156}," apiKey",[112,3816,160],{"class":125},[112,3818,3819],{"class":129}," apiKeyInput.value.",[112,3821,3822],{"class":163},"trim",[112,3824,462],{"class":129},[112,3826,3828,3830],{"class":114,"line":3827},49,[112,3829,3511],{"class":125},[112,3831,3370],{"class":129},[112,3833,3835,3838,3840,3843],{"class":114,"line":3834},50,[112,3836,3837],{"class":125},"      await",[112,3839,3341],{"class":129},[112,3841,3842],{"class":163},"set",[112,3844,3845],{"class":129},"({ apiKey });\n",[112,3847,3849,3851,3853],{"class":114,"line":3848},51,[112,3850,3837],{"class":125},[112,3852,3388],{"class":163},[112,3854,3855],{"class":129},"(apiKey);\n",[112,3857,3859],{"class":114,"line":3858},52,[112,3860,3118],{"class":129},[112,3862,3864,3867,3870],{"class":114,"line":3863},53,[112,3865,3866],{"class":129},"  }, ",[112,3868,3869],{"class":156},"500",[112,3871,635],{"class":129},[112,3873,3875],{"class":114,"line":3874},54,[112,3876,250],{"class":129},[112,3878,3880],{"class":114,"line":3879},55,[112,3881,147],{"emptyLinePlaceholder":146},[112,3883,3885,3888,3890,3892,3895,3897,3899,3901,3903],{"class":114,"line":3884},56,[112,3886,3887],{"class":129},"saveBtn.",[112,3889,3764],{"class":163},[112,3891,425],{"class":129},[112,3893,3894],{"class":136},"'click'",[112,3896,447],{"class":129},[112,3898,3305],{"class":125},[112,3900,3802],{"class":129},[112,3902,229],{"class":125},[112,3904,1294],{"class":129},[112,3906,3908,3910,3912,3914,3916,3918],{"class":114,"line":3907},57,[112,3909,1974],{"class":125},[112,3911,3814],{"class":156},[112,3913,160],{"class":125},[112,3915,3819],{"class":129},[112,3917,3822],{"class":163},[112,3919,462],{"class":129},[112,3921,3923,3925,3928,3930],{"class":114,"line":3922},58,[112,3924,1974],{"class":125},[112,3926,3927],{"class":156}," teamId",[112,3929,160],{"class":125},[112,3931,3932],{"class":129}," teamSelect.value;\n",[112,3934,3936],{"class":114,"line":3935},59,[112,3937,147],{"emptyLinePlaceholder":146},[112,3939,3941,3943,3946,3949],{"class":114,"line":3940},60,[112,3942,3367],{"class":125},[112,3944,3945],{"class":129}," (",[112,3947,3948],{"class":125},"!",[112,3950,3951],{"class":129},"apiKey) {\n",[112,3953,3955,3957,3959,3962,3964,3966],{"class":114,"line":3954},61,[112,3956,3719],{"class":163},[112,3958,425],{"class":129},[112,3960,3961],{"class":136},"'APIキーを入力してください'",[112,3963,447],{"class":129},[112,3965,3732],{"class":136},[112,3967,635],{"class":129},[112,3969,3971,3974],{"class":114,"line":3970},62,[112,3972,3973],{"class":125},"    return",[112,3975,140],{"class":129},[112,3977,3979],{"class":114,"line":3978},63,[112,3980,3232],{"class":129},[112,3982,3984],{"class":114,"line":3983},64,[112,3985,147],{"emptyLinePlaceholder":146},[112,3987,3989,3991,3993,3995],{"class":114,"line":3988},65,[112,3990,3367],{"class":125},[112,3992,3945],{"class":129},[112,3994,3948],{"class":125},[112,3996,3997],{"class":129},"teamId) {\n",[112,3999,4001,4003,4005,4008,4010,4012],{"class":114,"line":4000},66,[112,4002,3719],{"class":163},[112,4004,425],{"class":129},[112,4006,4007],{"class":136},"'チームを選択してください'",[112,4009,447],{"class":129},[112,4011,3732],{"class":136},[112,4013,635],{"class":129},[112,4015,4017,4019],{"class":114,"line":4016},67,[112,4018,3973],{"class":125},[112,4020,140],{"class":129},[112,4022,4024],{"class":114,"line":4023},68,[112,4025,3232],{"class":129},[112,4027,4029],{"class":114,"line":4028},69,[112,4030,147],{"emptyLinePlaceholder":146},[112,4032,4034,4036],{"class":114,"line":4033},70,[112,4035,3460],{"class":125},[112,4037,1294],{"class":129},[112,4039,4041,4043,4045,4047],{"class":114,"line":4040},71,[112,4042,3385],{"class":125},[112,4044,3341],{"class":129},[112,4046,3842],{"class":163},[112,4048,4049],{"class":129},"({ apiKey, teamId });\n",[112,4051,4053,4055,4057,4060,4062,4065],{"class":114,"line":4052},72,[112,4054,3719],{"class":163},[112,4056,425],{"class":129},[112,4058,4059],{"class":136},"'設定を保存しました'",[112,4061,447],{"class":129},[112,4063,4064],{"class":136},"'success'",[112,4066,635],{"class":129},[112,4068,4070,4072,4074],{"class":114,"line":4069},73,[112,4071,3683],{"class":129},[112,4073,3686],{"class":125},[112,4075,3689],{"class":129},[112,4077,4079,4081,4083,4086,4088,4090,4092],{"class":114,"line":4078},74,[112,4080,3719],{"class":163},[112,4082,425],{"class":129},[112,4084,4085],{"class":136},"'保存に失敗しました: '",[112,4087,3702],{"class":125},[112,4089,3729],{"class":129},[112,4091,3732],{"class":136},[112,4093,635],{"class":129},[112,4095,4097],{"class":114,"line":4096},75,[112,4098,3232],{"class":129},[112,4100,4102],{"class":114,"line":4101},76,[112,4103,250],{"class":129},[83,4105,4106],{"id":4106},"処理の流れ",[4108,4109,4110,4121,4128,4135],"ol",{},[34,4111,4112,4113,4116,4117,4120],{},"ページ読み込み時に ",[90,4114,4115],{},"init"," 関数が呼ばれ、",[90,4118,4119],{},"chrome.storage.sync.get"," で保存されている API キーとチーム ID を取得",[34,4122,4123,4124,4127],{},"background.js に ",[90,4125,4126],{},"{ action: 'fetchTeams' }"," を送信",[34,4129,4130,4131,4134],{},"レスポンスで ",[90,4132,4133],{},"\u003Cselect>"," を構築",[34,4136,4137],{},"保存ボタンがクリックされたときに API キーとチーム ID を保存",[11,4139,4141],{"id":4140},"popuppopupjs","popup\u002Fpopup.js",[15,4143,4144],{},"ツールバーアイコンをクリックしたときに表示されるポップアップのロジックです。",[103,4146,4148],{"className":1909,"code":4147,"language":1911,"meta":108,"style":108},"\u002F\u002F 5つのView（状態）を切り替える\nfunction showView(view) {\n  [setupView, mainView, successView, loadingView, errorView].forEach((v) => {\n    v.style.display = 'none';\n  });\n  view.style.display = 'flex';\n}\n\nasync function init() {\n  \u002F\u002F 設定が未完了なら setupView を表示\n  const { apiKey, teamId } = await chrome.storage.sync.get(['apiKey', 'teamId']);\n  if (!apiKey || !teamId) {\n    showView(setupView);\n    return;\n  }\n\n  \u002F\u002F 現在のタブ情報を取得（activeTab権限で許可）\n  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });\n  currentTab = tab;\n\n  titleInput.value = tab.title || '';\n  urlDisplay.textContent = tab.url || '';\n  showView(mainView);\n}\n\n\u002F\u002F 設定画面を開く\nopenOptionsBtn.addEventListener('click', () => {\n  chrome.runtime.openOptionsPage();\n});\n\n\u002F\u002F 「Linearに追加」ボタン\nclipBtn.addEventListener('click', async () => {\n  const title = titleInput.value.trim();\n\n  if (!title) {\n    errorMessage.textContent = 'タイトルを入力してください';\n    showView(errorView);\n    return;\n  }\n\n  showView(loadingView);\n\n  try {\n    const { teamId } = await chrome.storage.sync.get('teamId');\n\n    const response = await chrome.runtime.sendMessage({\n      action: 'createIssue',\n      teamId,\n      title,\n      url: currentTab.url,\n    });\n\n    if (response.error) {\n      throw new Error(response.error);\n    }\n\n    issueLink.textContent = response.issue.identifier;\n    issueLink.href = response.issue.url;\n    showView(successView);\n  } catch (error) {\n    errorMessage.textContent = error.message;\n    showView(errorView);\n  }\n});\n\nretryBtn.addEventListener('click', () => {\n  showView(mainView);\n});\n",[90,4149,4150,4155,4170,4188,4200,4204,4216,4220,4224,4234,4239,4271,4290,4298,4304,4308,4312,4317,4354,4364,4368,4385,4401,4409,4413,4417,4422,4439,4449,4453,4457,4462,4483,4499,4503,4514,4526,4533,4539,4543,4547,4554,4558,4564,4588,4592,4608,4617,4622,4627,4632,4636,4640,4646,4656,4660,4664,4674,4684,4691,4699,4708,4714,4718,4722,4726,4743,4749],{"__ignoreMap":108},[112,4151,4152],{"class":114,"line":115},[112,4153,4154],{"class":118},"\u002F\u002F 5つのView（状態）を切り替える\n",[112,4156,4157,4160,4163,4165,4168],{"class":114,"line":122},[112,4158,4159],{"class":125},"function",[112,4161,4162],{"class":163}," showView",[112,4164,425],{"class":129},[112,4166,4167],{"class":222},"view",[112,4169,1969],{"class":129},[112,4171,4172,4175,4177,4179,4182,4184,4186],{"class":114,"line":143},[112,4173,4174],{"class":129},"  [setupView, mainView, successView, loadingView, errorView].",[112,4176,3559],{"class":163},[112,4178,219],{"class":129},[112,4180,4181],{"class":222},"v",[112,4183,226],{"class":129},[112,4185,229],{"class":125},[112,4187,1294],{"class":129},[112,4189,4190,4193,4195,4198],{"class":114,"line":150},[112,4191,4192],{"class":129},"    v.style.display ",[112,4194,576],{"class":125},[112,4196,4197],{"class":136}," 'none'",[112,4199,140],{"class":129},[112,4201,4202],{"class":114,"line":170},[112,4203,1495],{"class":129},[112,4205,4206,4209,4211,4214],{"class":114,"line":182},[112,4207,4208],{"class":129},"  view.style.display ",[112,4210,576],{"class":125},[112,4212,4213],{"class":136}," 'flex'",[112,4215,140],{"class":129},[112,4217,4218],{"class":114,"line":193},[112,4219,1452],{"class":129},[112,4221,4222],{"class":114,"line":205},[112,4223,147],{"emptyLinePlaceholder":146},[112,4225,4226,4228,4230,4232],{"class":114,"line":241},[112,4227,3305],{"class":125},[112,4229,1958],{"class":125},[112,4231,3310],{"class":163},[112,4233,3313],{"class":129},[112,4235,4236],{"class":114,"line":247},[112,4237,4238],{"class":118},"  \u002F\u002F 設定が未完了なら setupView を表示\n",[112,4240,4241,4243,4245,4247,4249,4251,4253,4255,4257,4259,4261,4263,4265,4267,4269],{"class":114,"line":351},[112,4242,1974],{"class":125},[112,4244,561],{"class":129},[112,4246,3327],{"class":156},[112,4248,447],{"class":129},[112,4250,3332],{"class":156},[112,4252,573],{"class":129},[112,4254,576],{"class":125},[112,4256,419],{"class":125},[112,4258,3341],{"class":129},[112,4260,3344],{"class":163},[112,4262,3347],{"class":129},[112,4264,3350],{"class":136},[112,4266,447],{"class":129},[112,4268,3355],{"class":136},[112,4270,3358],{"class":129},[112,4272,4273,4275,4277,4279,4282,4285,4288],{"class":114,"line":361},[112,4274,3367],{"class":125},[112,4276,3945],{"class":129},[112,4278,3948],{"class":125},[112,4280,4281],{"class":129},"apiKey ",[112,4283,4284],{"class":125},"||",[112,4286,4287],{"class":125}," !",[112,4289,3997],{"class":129},[112,4291,4292,4295],{"class":114,"line":367},[112,4293,4294],{"class":163},"    showView",[112,4296,4297],{"class":129},"(setupView);\n",[112,4299,4300,4302],{"class":114,"line":373},[112,4301,3973],{"class":125},[112,4303,140],{"class":129},[112,4305,4306],{"class":114,"line":379},[112,4307,3232],{"class":129},[112,4309,4310],{"class":114,"line":1302},[112,4311,147],{"emptyLinePlaceholder":146},[112,4313,4314],{"class":114,"line":1502},[112,4315,4316],{"class":118},"  \u002F\u002F 現在のタブ情報を取得（activeTab権限で許可）\n",[112,4318,4319,4321,4324,4327,4330,4332,4334,4337,4340,4343,4346,4349,4351],{"class":114,"line":1507},[112,4320,1974],{"class":125},[112,4322,4323],{"class":129}," [",[112,4325,4326],{"class":156},"tab",[112,4328,4329],{"class":129},"] ",[112,4331,576],{"class":125},[112,4333,419],{"class":125},[112,4335,4336],{"class":129}," chrome.tabs.",[112,4338,4339],{"class":163},"query",[112,4341,4342],{"class":129},"({ active: ",[112,4344,4345],{"class":156},"true",[112,4347,4348],{"class":129},", currentWindow: ",[112,4350,4345],{"class":156},[112,4352,4353],{"class":129}," });\n",[112,4355,4356,4359,4361],{"class":114,"line":1512},[112,4357,4358],{"class":129},"  currentTab ",[112,4360,576],{"class":125},[112,4362,4363],{"class":129}," tab;\n",[112,4365,4366],{"class":114,"line":1518},[112,4367,147],{"emptyLinePlaceholder":146},[112,4369,4370,4373,4375,4378,4380,4383],{"class":114,"line":1524},[112,4371,4372],{"class":129},"  titleInput.value ",[112,4374,576],{"class":125},[112,4376,4377],{"class":129}," tab.title ",[112,4379,4284],{"class":125},[112,4381,4382],{"class":136}," ''",[112,4384,140],{"class":129},[112,4386,4387,4390,4392,4395,4397,4399],{"class":114,"line":1530},[112,4388,4389],{"class":129},"  urlDisplay.textContent ",[112,4391,576],{"class":125},[112,4393,4394],{"class":129}," tab.url ",[112,4396,4284],{"class":125},[112,4398,4382],{"class":136},[112,4400,140],{"class":129},[112,4402,4403,4406],{"class":114,"line":1536},[112,4404,4405],{"class":163},"  showView",[112,4407,4408],{"class":129},"(mainView);\n",[112,4410,4411],{"class":114,"line":1542},[112,4412,1452],{"class":129},[112,4414,4415],{"class":114,"line":2402},[112,4416,147],{"emptyLinePlaceholder":146},[112,4418,4419],{"class":114,"line":2407},[112,4420,4421],{"class":118},"\u002F\u002F 設定画面を開く\n",[112,4423,4424,4427,4429,4431,4433,4435,4437],{"class":114,"line":2413},[112,4425,4426],{"class":129},"openOptionsBtn.",[112,4428,3764],{"class":163},[112,4430,425],{"class":129},[112,4432,3894],{"class":136},[112,4434,595],{"class":129},[112,4436,229],{"class":125},[112,4438,1294],{"class":129},[112,4440,4441,4444,4447],{"class":114,"line":2446},[112,4442,4443],{"class":129},"  chrome.runtime.",[112,4445,4446],{"class":163},"openOptionsPage",[112,4448,462],{"class":129},[112,4450,4451],{"class":114,"line":2451},[112,4452,250],{"class":129},[112,4454,4455],{"class":114,"line":2464},[112,4456,147],{"emptyLinePlaceholder":146},[112,4458,4459],{"class":114,"line":2481},[112,4460,4461],{"class":118},"\u002F\u002F 「Linearに追加」ボタン\n",[112,4463,4464,4467,4469,4471,4473,4475,4477,4479,4481],{"class":114,"line":2486},[112,4465,4466],{"class":129},"clipBtn.",[112,4468,3764],{"class":163},[112,4470,425],{"class":129},[112,4472,3894],{"class":136},[112,4474,447],{"class":129},[112,4476,3305],{"class":125},[112,4478,3802],{"class":129},[112,4480,229],{"class":125},[112,4482,1294],{"class":129},[112,4484,4485,4487,4490,4492,4495,4497],{"class":114,"line":3229},[112,4486,1974],{"class":125},[112,4488,4489],{"class":156}," title",[112,4491,160],{"class":125},[112,4493,4494],{"class":129}," titleInput.value.",[112,4496,3822],{"class":163},[112,4498,462],{"class":129},[112,4500,4501],{"class":114,"line":3235},[112,4502,147],{"emptyLinePlaceholder":146},[112,4504,4505,4507,4509,4511],{"class":114,"line":3657},[112,4506,3367],{"class":125},[112,4508,3945],{"class":129},[112,4510,3948],{"class":125},[112,4512,4513],{"class":129},"title) {\n",[112,4515,4516,4519,4521,4524],{"class":114,"line":3662},[112,4517,4518],{"class":129},"    errorMessage.textContent ",[112,4520,576],{"class":125},[112,4522,4523],{"class":136}," 'タイトルを入力してください'",[112,4525,140],{"class":129},[112,4527,4528,4530],{"class":114,"line":3667},[112,4529,4294],{"class":163},[112,4531,4532],{"class":129},"(errorView);\n",[112,4534,4535,4537],{"class":114,"line":3680},[112,4536,3973],{"class":125},[112,4538,140],{"class":129},[112,4540,4541],{"class":114,"line":3692},[112,4542,3232],{"class":129},[112,4544,4545],{"class":114,"line":3716},[112,4546,147],{"emptyLinePlaceholder":146},[112,4548,4549,4551],{"class":114,"line":3737},[112,4550,4405],{"class":163},[112,4552,4553],{"class":129},"(loadingView);\n",[112,4555,4556],{"class":114,"line":3742},[112,4557,147],{"emptyLinePlaceholder":146},[112,4559,4560,4562],{"class":114,"line":3747},[112,4561,3460],{"class":125},[112,4563,1294],{"class":129},[112,4565,4566,4568,4570,4572,4574,4576,4578,4580,4582,4584,4586],{"class":114,"line":3752},[112,4567,3472],{"class":125},[112,4569,561],{"class":129},[112,4571,3332],{"class":156},[112,4573,573],{"class":129},[112,4575,576],{"class":125},[112,4577,419],{"class":125},[112,4579,3341],{"class":129},[112,4581,3344],{"class":163},[112,4583,425],{"class":129},[112,4585,3355],{"class":136},[112,4587,635],{"class":129},[112,4589,4590],{"class":114,"line":3758},[112,4591,147],{"emptyLinePlaceholder":146},[112,4593,4594,4596,4598,4600,4602,4604,4606],{"class":114,"line":3778},[112,4595,3472],{"class":125},[112,4597,3475],{"class":156},[112,4599,160],{"class":125},[112,4601,419],{"class":125},[112,4603,3482],{"class":129},[112,4605,3485],{"class":163},[112,4607,167],{"class":129},[112,4609,4610,4612,4615],{"class":114,"line":3787},[112,4611,3492],{"class":129},[112,4613,4614],{"class":136},"'createIssue'",[112,4616,179],{"class":129},[112,4618,4619],{"class":114,"line":3809},[112,4620,4621],{"class":129},"      teamId,\n",[112,4623,4624],{"class":114,"line":3827},[112,4625,4626],{"class":129},"      title,\n",[112,4628,4629],{"class":114,"line":3834},[112,4630,4631],{"class":129},"      url: currentTab.url,\n",[112,4633,4634],{"class":114,"line":3848},[112,4635,3502],{"class":129},[112,4637,4638],{"class":114,"line":3858},[112,4639,147],{"emptyLinePlaceholder":146},[112,4641,4642,4644],{"class":114,"line":3863},[112,4643,3511],{"class":125},[112,4645,3514],{"class":129},[112,4647,4648,4650,4652,4654],{"class":114,"line":3874},[112,4649,3519],{"class":125},[112,4651,232],{"class":125},[112,4653,3524],{"class":163},[112,4655,3527],{"class":129},[112,4657,4658],{"class":114,"line":3879},[112,4659,3118],{"class":129},[112,4661,4662],{"class":114,"line":3884},[112,4663,147],{"emptyLinePlaceholder":146},[112,4665,4666,4669,4671],{"class":114,"line":3907},[112,4667,4668],{"class":129},"    issueLink.textContent ",[112,4670,576],{"class":125},[112,4672,4673],{"class":129}," response.issue.identifier;\n",[112,4675,4676,4679,4681],{"class":114,"line":3922},[112,4677,4678],{"class":129},"    issueLink.href ",[112,4680,576],{"class":125},[112,4682,4683],{"class":129}," response.issue.url;\n",[112,4685,4686,4688],{"class":114,"line":3935},[112,4687,4294],{"class":163},[112,4689,4690],{"class":129},"(successView);\n",[112,4692,4693,4695,4697],{"class":114,"line":3940},[112,4694,3683],{"class":129},[112,4696,3686],{"class":125},[112,4698,3689],{"class":129},[112,4700,4701,4703,4705],{"class":114,"line":3954},[112,4702,4518],{"class":129},[112,4704,576],{"class":125},[112,4706,4707],{"class":129}," error.message;\n",[112,4709,4710,4712],{"class":114,"line":3970},[112,4711,4294],{"class":163},[112,4713,4532],{"class":129},[112,4715,4716],{"class":114,"line":3978},[112,4717,3232],{"class":129},[112,4719,4720],{"class":114,"line":3983},[112,4721,250],{"class":129},[112,4723,4724],{"class":114,"line":3988},[112,4725,147],{"emptyLinePlaceholder":146},[112,4727,4728,4731,4733,4735,4737,4739,4741],{"class":114,"line":4000},[112,4729,4730],{"class":129},"retryBtn.",[112,4732,3764],{"class":163},[112,4734,425],{"class":129},[112,4736,3894],{"class":136},[112,4738,595],{"class":129},[112,4740,229],{"class":125},[112,4742,1294],{"class":129},[112,4744,4745,4747],{"class":114,"line":4016},[112,4746,4405],{"class":163},[112,4748,4408],{"class":129},[112,4750,4751],{"class":114,"line":4023},[112,4752,250],{"class":129},[83,4754,4106],{"id":4755},"処理の流れ-1",[4108,4757,4758,4764,4769,4776,4782],{},[34,4759,4760,4761,4763],{},"ポップアップ表示時に ",[90,4762,4115],{}," が呼ばれる",[34,4765,4766,4768],{},[90,4767,4119],{}," で設定済みか確認",[34,4770,4771,4772,4775],{},"未設定なら setupView、設定済みなら ",[90,4773,4774],{},"chrome.tabs.query"," で現在タブの情報を取得",[34,4777,4778,4779,4127],{},"「Linear に追加」クリックで background.js に ",[90,4780,4781],{},"{ action: 'createIssue' }",[34,4783,4784],{},"結果に応じて successView \u002F errorView を表示",[11,4786,4788],{"id":4787},"scriptsbackgroundjs","scripts\u002Fbackground.js",[15,4790,4791],{},"popup.js や options.js からのメッセージを受け取り、API 通信を実行するメッセージハブです。",[103,4793,4795],{"className":1909,"code":4794,"language":1911,"meta":108,"style":108},"import { fetchTeams, createIssueWithLink } from '.\u002Flinear-api.js';\n\n\u002F\u002F メッセージを受信するリスナー\nchrome.runtime.onMessage.addListener((request, sender, sendResponse) => {\n  handleMessage(request)\n    .then(sendResponse)\n    .catch((error) => sendResponse({ error: error.message }));\n  return true; \u002F\u002F 非同期でレスポンスを返すために必要\n});\n\nasync function handleMessage(request) {\n  const { apiKey } = await chrome.storage.sync.get('apiKey');\n\n  if (!apiKey) {\n    throw new Error('APIキーが設定されていません。設定画面で設定してください。');\n  }\n\n  switch (request.action) {\n    case 'fetchTeams':\n      return { teams: await fetchTeams(apiKey) };\n\n    case 'createIssue': {\n      const { teamId, title, url } = request;\n      const issue = await createIssueWithLink(apiKey, teamId, title, url);\n      return { issue };\n    }\n\n    default:\n      throw new Error(`Unknown action: ${request.action}`);\n  }\n}\n",[90,4796,4797,4811,4815,4820,4849,4857,4868,4889,4901,4905,4909,4924,4948,4952,4962,4978,4982,4986,4994,5005,5022,5026,5035,5060,5077,5084,5088,5092,5099,5123,5127],{"__ignoreMap":108},[112,4798,4799,4801,4804,4806,4809],{"class":114,"line":115},[112,4800,126],{"class":125},[112,4802,4803],{"class":129}," { fetchTeams, createIssueWithLink } ",[112,4805,133],{"class":125},[112,4807,4808],{"class":136}," '.\u002Flinear-api.js'",[112,4810,140],{"class":129},[112,4812,4813],{"class":114,"line":122},[112,4814,147],{"emptyLinePlaceholder":146},[112,4816,4817],{"class":114,"line":143},[112,4818,4819],{"class":118},"\u002F\u002F メッセージを受信するリスナー\n",[112,4821,4822,4825,4828,4830,4833,4835,4838,4840,4843,4845,4847],{"class":114,"line":150},[112,4823,4824],{"class":129},"chrome.runtime.onMessage.",[112,4826,4827],{"class":163},"addListener",[112,4829,219],{"class":129},[112,4831,4832],{"class":222},"request",[112,4834,447],{"class":129},[112,4836,4837],{"class":222},"sender",[112,4839,447],{"class":129},[112,4841,4842],{"class":222},"sendResponse",[112,4844,226],{"class":129},[112,4846,229],{"class":125},[112,4848,1294],{"class":129},[112,4850,4851,4854],{"class":114,"line":170},[112,4852,4853],{"class":163},"  handleMessage",[112,4855,4856],{"class":129},"(request)\n",[112,4858,4859,4862,4865],{"class":114,"line":182},[112,4860,4861],{"class":129},"    .",[112,4863,4864],{"class":163},"then",[112,4866,4867],{"class":129},"(sendResponse)\n",[112,4869,4870,4872,4874,4876,4879,4881,4883,4886],{"class":114,"line":193},[112,4871,4861],{"class":129},[112,4873,3686],{"class":163},[112,4875,219],{"class":129},[112,4877,4878],{"class":222},"error",[112,4880,226],{"class":129},[112,4882,229],{"class":125},[112,4884,4885],{"class":163}," sendResponse",[112,4887,4888],{"class":129},"({ error: error.message }));\n",[112,4890,4891,4893,4895,4898],{"class":114,"line":205},[112,4892,2048],{"class":125},[112,4894,3437],{"class":156},[112,4896,4897],{"class":129},"; ",[112,4899,4900],{"class":118},"\u002F\u002F 非同期でレスポンスを返すために必要\n",[112,4902,4903],{"class":114,"line":241},[112,4904,250],{"class":129},[112,4906,4907],{"class":114,"line":247},[112,4908,147],{"emptyLinePlaceholder":146},[112,4910,4911,4913,4915,4918,4920,4922],{"class":114,"line":351},[112,4912,3305],{"class":125},[112,4914,1958],{"class":125},[112,4916,4917],{"class":163}," handleMessage",[112,4919,425],{"class":129},[112,4921,4832],{"class":222},[112,4923,1969],{"class":129},[112,4925,4926,4928,4930,4932,4934,4936,4938,4940,4942,4944,4946],{"class":114,"line":361},[112,4927,1974],{"class":125},[112,4929,561],{"class":129},[112,4931,3327],{"class":156},[112,4933,573],{"class":129},[112,4935,576],{"class":125},[112,4937,419],{"class":125},[112,4939,3341],{"class":129},[112,4941,3344],{"class":163},[112,4943,425],{"class":129},[112,4945,3350],{"class":136},[112,4947,635],{"class":129},[112,4949,4950],{"class":114,"line":367},[112,4951,147],{"emptyLinePlaceholder":146},[112,4953,4954,4956,4958,4960],{"class":114,"line":373},[112,4955,3367],{"class":125},[112,4957,3945],{"class":129},[112,4959,3948],{"class":125},[112,4961,3951],{"class":129},[112,4963,4964,4967,4969,4971,4973,4976],{"class":114,"line":379},[112,4965,4966],{"class":125},"    throw",[112,4968,232],{"class":125},[112,4970,3524],{"class":163},[112,4972,425],{"class":129},[112,4974,4975],{"class":136},"'APIキーが設定されていません。設定画面で設定してください。'",[112,4977,635],{"class":129},[112,4979,4980],{"class":114,"line":1302},[112,4981,3232],{"class":129},[112,4983,4984],{"class":114,"line":1502},[112,4985,147],{"emptyLinePlaceholder":146},[112,4987,4988,4991],{"class":114,"line":1507},[112,4989,4990],{"class":125},"  switch",[112,4992,4993],{"class":129}," (request.action) {\n",[112,4995,4996,4999,5002],{"class":114,"line":1512},[112,4997,4998],{"class":125},"    case",[112,5000,5001],{"class":136}," 'fetchTeams'",[112,5003,5004],{"class":129},":\n",[112,5006,5007,5010,5013,5016,5019],{"class":114,"line":1518},[112,5008,5009],{"class":125},"      return",[112,5011,5012],{"class":129}," { teams: ",[112,5014,5015],{"class":125},"await",[112,5017,5018],{"class":163}," fetchTeams",[112,5020,5021],{"class":129},"(apiKey) };\n",[112,5023,5024],{"class":114,"line":1524},[112,5025,147],{"emptyLinePlaceholder":146},[112,5027,5028,5030,5033],{"class":114,"line":1530},[112,5029,4998],{"class":125},[112,5031,5032],{"class":136}," 'createIssue'",[112,5034,852],{"class":129},[112,5036,5037,5039,5041,5043,5045,5048,5050,5053,5055,5057],{"class":114,"line":1536},[112,5038,3575],{"class":125},[112,5040,561],{"class":129},[112,5042,3332],{"class":156},[112,5044,447],{"class":129},[112,5046,5047],{"class":156},"title",[112,5049,447],{"class":129},[112,5051,5052],{"class":156},"url",[112,5054,573],{"class":129},[112,5056,576],{"class":125},[112,5058,5059],{"class":129}," request;\n",[112,5061,5062,5064,5067,5069,5071,5074],{"class":114,"line":1542},[112,5063,3575],{"class":125},[112,5065,5066],{"class":156}," issue",[112,5068,160],{"class":125},[112,5070,419],{"class":125},[112,5072,5073],{"class":163}," createIssueWithLink",[112,5075,5076],{"class":129},"(apiKey, teamId, title, url);\n",[112,5078,5079,5081],{"class":114,"line":2402},[112,5080,5009],{"class":125},[112,5082,5083],{"class":129}," { issue };\n",[112,5085,5086],{"class":114,"line":2407},[112,5087,3118],{"class":129},[112,5089,5090],{"class":114,"line":2413},[112,5091,147],{"emptyLinePlaceholder":146},[112,5093,5094,5097],{"class":114,"line":2446},[112,5095,5096],{"class":125},"    default",[112,5098,5004],{"class":129},[112,5100,5101,5103,5105,5107,5109,5112,5114,5116,5119,5121],{"class":114,"line":2451},[112,5102,3519],{"class":125},[112,5104,232],{"class":125},[112,5106,3524],{"class":163},[112,5108,425],{"class":129},[112,5110,5111],{"class":136},"`Unknown action: ${",[112,5113,4832],{"class":129},[112,5115,2124],{"class":136},[112,5117,5118],{"class":129},"action",[112,5120,592],{"class":136},[112,5122,635],{"class":129},[112,5124,5125],{"class":114,"line":2464},[112,5126,3232],{"class":129},[112,5128,5129],{"class":114,"line":2481},[112,5130,1452],{"class":129},[83,5132,5133],{"id":5133},"重要なポイント",[31,5135,5136,5145,5148],{},[34,5137,5138,5141,5142,5144],{},[90,5139,5140],{},"return true"," が重要。これがないと ",[90,5143,4842],{}," が非同期で呼ばれる前に接続が切れる",[34,5146,5147],{},"API キーは storage から取得（popup\u002Foptions から渡さない設計）",[34,5149,5150,5151,5154],{},"実際の API 通信は ",[90,5152,5153],{},"linear-api.js"," に委譲",[11,5156,5158],{"id":5157},"scriptslinear-apijs","scripts\u002Flinear-api.js",[15,5160,5161],{},"Linear GraphQL API との通信を担当するモジュールです。",[103,5163,5165],{"className":1909,"code":5164,"language":1911,"meta":108,"style":108},"const LINEAR_API_ENDPOINT = 'https:\u002F\u002Fapi.linear.app\u002Fgraphql';\n\n\u002F\u002F GraphQLリクエストの共通処理\nexport async function linearFetch(apiKey, query, variables = {}) {\n  const response = await fetch(LINEAR_API_ENDPOINT, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      'Authorization': apiKey,  \u002F\u002F Bearerなしで直接APIキー\n    },\n    body: JSON.stringify({ query, variables }),\n  });\n\n  if (!response.ok) {\n    throw new Error(`HTTP error: ${response.status}`);\n  }\n\n  const result = await response.json();\n\n  if (result.errors) {\n    throw new Error(result.errors[0].message);\n  }\n\n  return result.data;\n}\n\n\u002F\u002F チーム一覧を取得\nexport async function fetchTeams(apiKey) {\n  const query = `\n    query Teams {\n      teams {\n        nodes {\n          id\n          name\n        }\n      }\n    }\n  `;\n\n  const data = await linearFetch(apiKey, query);\n  return data.teams.nodes;\n}\n\n\u002F\u002F イシューを作成\nexport async function createIssue(apiKey, teamId, title) {\n  const mutation = `\n    mutation IssueCreate($title: String!, $teamId: String!) {\n      issueCreate(input: { title: $title, teamId: $teamId }) {\n        success\n        issue {\n          id\n          identifier\n          url\n        }\n      }\n    }\n  `;\n\n  const data = await linearFetch(apiKey, mutation, { title, teamId });\n\n  if (!data.issueCreate.success) {\n    throw new Error('Failed to create issue');\n  }\n\n  return data.issueCreate.issue;\n}\n\n\u002F\u002F イシューにURLを添付\nexport async function addLinkToIssue(apiKey, issueId, url, title) {\n  const mutation = `\n    mutation AttachmentLinkURL($issueId: String!, $url: String!, $title: String) {\n      attachmentLinkURL(issueId: $issueId, url: $url, title: $title) {\n        success\n        attachment {\n          id\n        }\n      }\n    }\n  `;\n\n  const data = await linearFetch(apiKey, mutation, { issueId, url, title });\n\n  if (!data.attachmentLinkURL.success) {\n    throw new Error('Failed to add link to issue');\n  }\n\n  return data.attachmentLinkURL.attachment;\n}\n\n\u002F\u002F イシュー作成 + URL添付をまとめて実行\nexport async function createIssueWithLink(apiKey, teamId, title, url) {\n  const issue = await createIssue(apiKey, teamId, title);\n  await addLinkToIssue(apiKey, issue.id, url, title);\n  return issue;\n}\n",[90,5166,5167,5181,5185,5190,5219,5240,5250,5255,5267,5278,5282,5298,5302,5306,5317,5342,5346,5350,5368,5372,5379,5396,5400,5404,5411,5415,5419,5424,5440,5452,5457,5462,5467,5472,5477,5482,5486,5490,5497,5501,5517,5524,5528,5532,5537,5562,5573,5578,5583,5588,5593,5597,5602,5607,5611,5615,5619,5625,5629,5644,5648,5659,5674,5678,5682,5689,5693,5697,5702,5732,5742,5747,5752,5756,5761,5765,5769,5774,5779,5786,5791,5807,5812,5824,5840,5845,5850,5858,5863,5868,5874,5903,5919,5930,5938],{"__ignoreMap":108},[112,5168,5169,5171,5174,5176,5179],{"class":114,"line":115},[112,5170,153],{"class":125},[112,5172,5173],{"class":156}," LINEAR_API_ENDPOINT",[112,5175,160],{"class":125},[112,5177,5178],{"class":136}," 'https:\u002F\u002Fapi.linear.app\u002Fgraphql'",[112,5180,140],{"class":129},[112,5182,5183],{"class":114,"line":122},[112,5184,147],{"emptyLinePlaceholder":146},[112,5186,5187],{"class":114,"line":143},[112,5188,5189],{"class":118},"\u002F\u002F GraphQLリクエストの共通処理\n",[112,5191,5192,5194,5196,5198,5201,5203,5205,5207,5209,5211,5214,5216],{"class":114,"line":150},[112,5193,288],{"class":125},[112,5195,1955],{"class":125},[112,5197,1958],{"class":125},[112,5199,5200],{"class":163}," linearFetch",[112,5202,425],{"class":129},[112,5204,3327],{"class":222},[112,5206,447],{"class":129},[112,5208,4339],{"class":222},[112,5210,447],{"class":129},[112,5212,5213],{"class":222},"variables",[112,5215,160],{"class":125},[112,5217,5218],{"class":129}," {}) {\n",[112,5220,5221,5223,5225,5227,5229,5232,5234,5237],{"class":114,"line":170},[112,5222,1974],{"class":125},[112,5224,3475],{"class":156},[112,5226,160],{"class":125},[112,5228,419],{"class":125},[112,5230,5231],{"class":163}," fetch",[112,5233,425],{"class":129},[112,5235,5236],{"class":156},"LINEAR_API_ENDPOINT",[112,5238,5239],{"class":129},", {\n",[112,5241,5242,5245,5248],{"class":114,"line":182},[112,5243,5244],{"class":129},"    method: ",[112,5246,5247],{"class":136},"'POST'",[112,5249,179],{"class":129},[112,5251,5252],{"class":114,"line":193},[112,5253,5254],{"class":129},"    headers: {\n",[112,5256,5257,5260,5262,5265],{"class":114,"line":205},[112,5258,5259],{"class":136},"      'Content-Type'",[112,5261,567],{"class":129},[112,5263,5264],{"class":136},"'application\u002Fjson'",[112,5266,179],{"class":129},[112,5268,5269,5272,5275],{"class":114,"line":241},[112,5270,5271],{"class":136},"      'Authorization'",[112,5273,5274],{"class":129},": apiKey,  ",[112,5276,5277],{"class":118},"\u002F\u002F Bearerなしで直接APIキー\n",[112,5279,5280],{"class":114,"line":247},[112,5281,2378],{"class":129},[112,5283,5284,5287,5290,5292,5295],{"class":114,"line":351},[112,5285,5286],{"class":129},"    body: ",[112,5288,5289],{"class":156},"JSON",[112,5291,2124],{"class":129},[112,5293,5294],{"class":163},"stringify",[112,5296,5297],{"class":129},"({ query, variables }),\n",[112,5299,5300],{"class":114,"line":361},[112,5301,1495],{"class":129},[112,5303,5304],{"class":114,"line":367},[112,5305,147],{"emptyLinePlaceholder":146},[112,5307,5308,5310,5312,5314],{"class":114,"line":373},[112,5309,3367],{"class":125},[112,5311,3945],{"class":129},[112,5313,3948],{"class":125},[112,5315,5316],{"class":129},"response.ok) {\n",[112,5318,5319,5321,5323,5325,5327,5330,5333,5335,5338,5340],{"class":114,"line":379},[112,5320,4966],{"class":125},[112,5322,232],{"class":125},[112,5324,3524],{"class":163},[112,5326,425],{"class":129},[112,5328,5329],{"class":136},"`HTTP error: ${",[112,5331,5332],{"class":129},"response",[112,5334,2124],{"class":136},[112,5336,5337],{"class":129},"status",[112,5339,592],{"class":136},[112,5341,635],{"class":129},[112,5343,5344],{"class":114,"line":1302},[112,5345,3232],{"class":129},[112,5347,5348],{"class":114,"line":1502},[112,5349,147],{"emptyLinePlaceholder":146},[112,5351,5352,5354,5357,5359,5361,5364,5366],{"class":114,"line":1507},[112,5353,1974],{"class":125},[112,5355,5356],{"class":156}," result",[112,5358,160],{"class":125},[112,5360,419],{"class":125},[112,5362,5363],{"class":129}," response.",[112,5365,2958],{"class":163},[112,5367,462],{"class":129},[112,5369,5370],{"class":114,"line":1512},[112,5371,147],{"emptyLinePlaceholder":146},[112,5373,5374,5376],{"class":114,"line":1518},[112,5375,3367],{"class":125},[112,5377,5378],{"class":129}," (result.errors) {\n",[112,5380,5381,5383,5385,5387,5390,5393],{"class":114,"line":1524},[112,5382,4966],{"class":125},[112,5384,232],{"class":125},[112,5386,3524],{"class":163},[112,5388,5389],{"class":129},"(result.errors[",[112,5391,5392],{"class":156},"0",[112,5394,5395],{"class":129},"].message);\n",[112,5397,5398],{"class":114,"line":1530},[112,5399,3232],{"class":129},[112,5401,5402],{"class":114,"line":1536},[112,5403,147],{"emptyLinePlaceholder":146},[112,5405,5406,5408],{"class":114,"line":1542},[112,5407,2048],{"class":125},[112,5409,5410],{"class":129}," result.data;\n",[112,5412,5413],{"class":114,"line":2402},[112,5414,1452],{"class":129},[112,5416,5417],{"class":114,"line":2407},[112,5418,147],{"emptyLinePlaceholder":146},[112,5420,5421],{"class":114,"line":2413},[112,5422,5423],{"class":118},"\u002F\u002F チーム一覧を取得\n",[112,5425,5426,5428,5430,5432,5434,5436,5438],{"class":114,"line":2446},[112,5427,288],{"class":125},[112,5429,1955],{"class":125},[112,5431,1958],{"class":125},[112,5433,5018],{"class":163},[112,5435,425],{"class":129},[112,5437,3327],{"class":222},[112,5439,1969],{"class":129},[112,5441,5442,5444,5447,5449],{"class":114,"line":2451},[112,5443,1974],{"class":125},[112,5445,5446],{"class":156}," query",[112,5448,160],{"class":125},[112,5450,5451],{"class":136}," `\n",[112,5453,5454],{"class":114,"line":2464},[112,5455,5456],{"class":136},"    query Teams {\n",[112,5458,5459],{"class":114,"line":2481},[112,5460,5461],{"class":136},"      teams {\n",[112,5463,5464],{"class":114,"line":2486},[112,5465,5466],{"class":136},"        nodes {\n",[112,5468,5469],{"class":114,"line":3229},[112,5470,5471],{"class":136},"          id\n",[112,5473,5474],{"class":114,"line":3235},[112,5475,5476],{"class":136},"          name\n",[112,5478,5479],{"class":114,"line":3657},[112,5480,5481],{"class":136},"        }\n",[112,5483,5484],{"class":114,"line":3662},[112,5485,3643],{"class":136},[112,5487,5488],{"class":114,"line":3667},[112,5489,3118],{"class":136},[112,5491,5492,5495],{"class":114,"line":3680},[112,5493,5494],{"class":136},"  `",[112,5496,140],{"class":129},[112,5498,5499],{"class":114,"line":3692},[112,5500,147],{"emptyLinePlaceholder":146},[112,5502,5503,5505,5508,5510,5512,5514],{"class":114,"line":3716},[112,5504,1974],{"class":125},[112,5506,5507],{"class":156}," data",[112,5509,160],{"class":125},[112,5511,419],{"class":125},[112,5513,5200],{"class":163},[112,5515,5516],{"class":129},"(apiKey, query);\n",[112,5518,5519,5521],{"class":114,"line":3737},[112,5520,2048],{"class":125},[112,5522,5523],{"class":129}," data.teams.nodes;\n",[112,5525,5526],{"class":114,"line":3742},[112,5527,1452],{"class":129},[112,5529,5530],{"class":114,"line":3747},[112,5531,147],{"emptyLinePlaceholder":146},[112,5533,5534],{"class":114,"line":3752},[112,5535,5536],{"class":118},"\u002F\u002F イシューを作成\n",[112,5538,5539,5541,5543,5545,5548,5550,5552,5554,5556,5558,5560],{"class":114,"line":3758},[112,5540,288],{"class":125},[112,5542,1955],{"class":125},[112,5544,1958],{"class":125},[112,5546,5547],{"class":163}," createIssue",[112,5549,425],{"class":129},[112,5551,3327],{"class":222},[112,5553,447],{"class":129},[112,5555,3332],{"class":222},[112,5557,447],{"class":129},[112,5559,5047],{"class":222},[112,5561,1969],{"class":129},[112,5563,5564,5566,5569,5571],{"class":114,"line":3778},[112,5565,1974],{"class":125},[112,5567,5568],{"class":156}," mutation",[112,5570,160],{"class":125},[112,5572,5451],{"class":136},[112,5574,5575],{"class":114,"line":3787},[112,5576,5577],{"class":136},"    mutation IssueCreate($title: String!, $teamId: String!) {\n",[112,5579,5580],{"class":114,"line":3809},[112,5581,5582],{"class":136},"      issueCreate(input: { title: $title, teamId: $teamId }) {\n",[112,5584,5585],{"class":114,"line":3827},[112,5586,5587],{"class":136},"        success\n",[112,5589,5590],{"class":114,"line":3834},[112,5591,5592],{"class":136},"        issue {\n",[112,5594,5595],{"class":114,"line":3848},[112,5596,5471],{"class":136},[112,5598,5599],{"class":114,"line":3858},[112,5600,5601],{"class":136},"          identifier\n",[112,5603,5604],{"class":114,"line":3863},[112,5605,5606],{"class":136},"          url\n",[112,5608,5609],{"class":114,"line":3874},[112,5610,5481],{"class":136},[112,5612,5613],{"class":114,"line":3879},[112,5614,3643],{"class":136},[112,5616,5617],{"class":114,"line":3884},[112,5618,3118],{"class":136},[112,5620,5621,5623],{"class":114,"line":3907},[112,5622,5494],{"class":136},[112,5624,140],{"class":129},[112,5626,5627],{"class":114,"line":3922},[112,5628,147],{"emptyLinePlaceholder":146},[112,5630,5631,5633,5635,5637,5639,5641],{"class":114,"line":3935},[112,5632,1974],{"class":125},[112,5634,5507],{"class":156},[112,5636,160],{"class":125},[112,5638,419],{"class":125},[112,5640,5200],{"class":163},[112,5642,5643],{"class":129},"(apiKey, mutation, { title, teamId });\n",[112,5645,5646],{"class":114,"line":3940},[112,5647,147],{"emptyLinePlaceholder":146},[112,5649,5650,5652,5654,5656],{"class":114,"line":3954},[112,5651,3367],{"class":125},[112,5653,3945],{"class":129},[112,5655,3948],{"class":125},[112,5657,5658],{"class":129},"data.issueCreate.success) {\n",[112,5660,5661,5663,5665,5667,5669,5672],{"class":114,"line":3970},[112,5662,4966],{"class":125},[112,5664,232],{"class":125},[112,5666,3524],{"class":163},[112,5668,425],{"class":129},[112,5670,5671],{"class":136},"'Failed to create issue'",[112,5673,635],{"class":129},[112,5675,5676],{"class":114,"line":3978},[112,5677,3232],{"class":129},[112,5679,5680],{"class":114,"line":3983},[112,5681,147],{"emptyLinePlaceholder":146},[112,5683,5684,5686],{"class":114,"line":3988},[112,5685,2048],{"class":125},[112,5687,5688],{"class":129}," data.issueCreate.issue;\n",[112,5690,5691],{"class":114,"line":4000},[112,5692,1452],{"class":129},[112,5694,5695],{"class":114,"line":4016},[112,5696,147],{"emptyLinePlaceholder":146},[112,5698,5699],{"class":114,"line":4023},[112,5700,5701],{"class":118},"\u002F\u002F イシューにURLを添付\n",[112,5703,5704,5706,5708,5710,5713,5715,5717,5719,5722,5724,5726,5728,5730],{"class":114,"line":4028},[112,5705,288],{"class":125},[112,5707,1955],{"class":125},[112,5709,1958],{"class":125},[112,5711,5712],{"class":163}," addLinkToIssue",[112,5714,425],{"class":129},[112,5716,3327],{"class":222},[112,5718,447],{"class":129},[112,5720,5721],{"class":222},"issueId",[112,5723,447],{"class":129},[112,5725,5052],{"class":222},[112,5727,447],{"class":129},[112,5729,5047],{"class":222},[112,5731,1969],{"class":129},[112,5733,5734,5736,5738,5740],{"class":114,"line":4033},[112,5735,1974],{"class":125},[112,5737,5568],{"class":156},[112,5739,160],{"class":125},[112,5741,5451],{"class":136},[112,5743,5744],{"class":114,"line":4040},[112,5745,5746],{"class":136},"    mutation AttachmentLinkURL($issueId: String!, $url: String!, $title: String) {\n",[112,5748,5749],{"class":114,"line":4052},[112,5750,5751],{"class":136},"      attachmentLinkURL(issueId: $issueId, url: $url, title: $title) {\n",[112,5753,5754],{"class":114,"line":4069},[112,5755,5587],{"class":136},[112,5757,5758],{"class":114,"line":4078},[112,5759,5760],{"class":136},"        attachment {\n",[112,5762,5763],{"class":114,"line":4096},[112,5764,5471],{"class":136},[112,5766,5767],{"class":114,"line":4101},[112,5768,5481],{"class":136},[112,5770,5772],{"class":114,"line":5771},77,[112,5773,3643],{"class":136},[112,5775,5777],{"class":114,"line":5776},78,[112,5778,3118],{"class":136},[112,5780,5782,5784],{"class":114,"line":5781},79,[112,5783,5494],{"class":136},[112,5785,140],{"class":129},[112,5787,5789],{"class":114,"line":5788},80,[112,5790,147],{"emptyLinePlaceholder":146},[112,5792,5794,5796,5798,5800,5802,5804],{"class":114,"line":5793},81,[112,5795,1974],{"class":125},[112,5797,5507],{"class":156},[112,5799,160],{"class":125},[112,5801,419],{"class":125},[112,5803,5200],{"class":163},[112,5805,5806],{"class":129},"(apiKey, mutation, { issueId, url, title });\n",[112,5808,5810],{"class":114,"line":5809},82,[112,5811,147],{"emptyLinePlaceholder":146},[112,5813,5815,5817,5819,5821],{"class":114,"line":5814},83,[112,5816,3367],{"class":125},[112,5818,3945],{"class":129},[112,5820,3948],{"class":125},[112,5822,5823],{"class":129},"data.attachmentLinkURL.success) {\n",[112,5825,5827,5829,5831,5833,5835,5838],{"class":114,"line":5826},84,[112,5828,4966],{"class":125},[112,5830,232],{"class":125},[112,5832,3524],{"class":163},[112,5834,425],{"class":129},[112,5836,5837],{"class":136},"'Failed to add link to issue'",[112,5839,635],{"class":129},[112,5841,5843],{"class":114,"line":5842},85,[112,5844,3232],{"class":129},[112,5846,5848],{"class":114,"line":5847},86,[112,5849,147],{"emptyLinePlaceholder":146},[112,5851,5853,5855],{"class":114,"line":5852},87,[112,5854,2048],{"class":125},[112,5856,5857],{"class":129}," data.attachmentLinkURL.attachment;\n",[112,5859,5861],{"class":114,"line":5860},88,[112,5862,1452],{"class":129},[112,5864,5866],{"class":114,"line":5865},89,[112,5867,147],{"emptyLinePlaceholder":146},[112,5869,5871],{"class":114,"line":5870},90,[112,5872,5873],{"class":118},"\u002F\u002F イシュー作成 + URL添付をまとめて実行\n",[112,5875,5877,5879,5881,5883,5885,5887,5889,5891,5893,5895,5897,5899,5901],{"class":114,"line":5876},91,[112,5878,288],{"class":125},[112,5880,1955],{"class":125},[112,5882,1958],{"class":125},[112,5884,5073],{"class":163},[112,5886,425],{"class":129},[112,5888,3327],{"class":222},[112,5890,447],{"class":129},[112,5892,3332],{"class":222},[112,5894,447],{"class":129},[112,5896,5047],{"class":222},[112,5898,447],{"class":129},[112,5900,5052],{"class":222},[112,5902,1969],{"class":129},[112,5904,5906,5908,5910,5912,5914,5916],{"class":114,"line":5905},92,[112,5907,1974],{"class":125},[112,5909,5066],{"class":156},[112,5911,160],{"class":125},[112,5913,419],{"class":125},[112,5915,5547],{"class":163},[112,5917,5918],{"class":129},"(apiKey, teamId, title);\n",[112,5920,5922,5925,5927],{"class":114,"line":5921},93,[112,5923,5924],{"class":125},"  await",[112,5926,5712],{"class":163},[112,5928,5929],{"class":129},"(apiKey, issue.id, url, title);\n",[112,5931,5933,5935],{"class":114,"line":5932},94,[112,5934,2048],{"class":125},[112,5936,5937],{"class":129}," issue;\n",[112,5939,5941],{"class":114,"line":5940},95,[112,5942,1452],{"class":129},[11,5944,5945],{"id":5945},"全体のアーキテクチャ",[103,5947,5952],{"className":5948,"code":5950,"language":5951},[5949],"language-text","popup.js \u002F options.js\n    ↓ chrome.runtime.sendMessage()\nbackground.js (メッセージハブ)\n    ↓ import\nlinear-api.js (API通信)\n    ↓ fetch\nLinear GraphQL API\n","text",[90,5953,5950],{"__ignoreMap":108},[15,5955,5956],{},"この設計により、UI 層（popup\u002Foptions）と API 通信層（linear-api.js）が分離され、保守性が高くなっています。また、background.js がメッセージハブとして機能することで、API キーの管理を一元化できています。",[11,5958,919],{"id":919},[15,5960,5961],{},"Chrome 拡張機能 Manifest V3 の基本的な構成と、Linear GraphQL API を使ったイシュー作成の実装を紹介しました。Service Worker ベースの background.js を使ったメッセージパッシングのパターンは、他の API 連携にも応用できるかと思います。",[944,5963,5964],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":108,"searchDepth":122,"depth":122,"links":5966},[5967,5968,5969,5973,5976,5979,5982,5983,5984],{"id":13,"depth":122,"text":13},{"id":2930,"depth":122,"text":2930},{"id":2949,"depth":122,"text":2950,"children":5970},[5971,5972],{"id":3240,"depth":143,"text":3241},{"id":3264,"depth":143,"text":3264},{"id":3291,"depth":122,"text":3292,"children":5974},[5975],{"id":4106,"depth":143,"text":4106},{"id":4140,"depth":122,"text":4141,"children":5977},[5978],{"id":4755,"depth":143,"text":4106},{"id":4787,"depth":122,"text":4788,"children":5980},[5981],{"id":5133,"depth":143,"text":5133},{"id":5157,"depth":122,"text":5158},{"id":5945,"depth":122,"text":5945},{"id":919,"depth":122,"text":919},"2026-01-04","現在のページURLをLinearイシューにクリップするChrome拡張機能を作成しました。Manifest V3、GraphQL API、Chrome Storage APIの使い方を解説します。",{"tags":5988},[5989,5990,1911],"chrome-extension","linear","\u002Fblog\u002Flinear-clipper-extension",{"title":2914,"description":5986},"blog\u002Flinear-clipper-extension","i-iwux5J_m84OiPok5BY38rIUoo5INDJ-eHpxa24zFU",{"id":5996,"title":5997,"body":5998,"date":6078,"description":6079,"extension":962,"meta":6080,"navigation":146,"path":6083,"seo":6084,"stem":6085,"__hash__":6086},"blog\u002Fblog\u002F2025-review.md","2025年の振り返り",{"type":8,"value":5999,"toc":6071},[6000,6002,6005,6008,6011,6014,6017,6020,6024,6027,6038,6041,6044,6047,6050,6053,6056,6059,6062,6065,6068],[11,6001,13],{"id":13},[15,6003,6004],{},"2025年は仕事もプライベートも大きな変化があった一年でした。転職、結婚、マンション購入と、人生の転機がこんなに重なることがあるんだなと驚いています。慌ただしくも充実した一年を振り返ります。",[11,6006,6007],{"id":6007},"仕事",[15,6009,6010],{},"今年は転職し、デザインシステムの開発や横断的な技術支援、技術広報などを担当しました。",[15,6012,6013],{},"いろいろな領域に関わる中で「自分がなんとかやるんや...!」という気持ちで走り続けた結果、ある日「もう何もしたくない」と思ってしまう日がありました。正直、自分がそうなるとは思っていなかったので驚きました。ただ、この経験があったからこそ「一人で抱え込まない」ことの大切さを本当の意味で理解できたと思います。",[15,6015,6016],{},"辛いときに社外で話ができるコミュニティがあったのは救いでした。コミュニティの皆様には本当に感謝しています。",[15,6018,6019],{},"来年はまた新しい環境でのチャレンジが始まります。今年の学びを活かして、周りを頼りながらやっていきたいと思います。",[11,6021,6023],{"id":6022},"登壇アウトプット","登壇・アウトプット",[15,6025,6026],{},"今年は社外での登壇やカンファレンスのコアスタッフに取り組みました。",[31,6028,6029,6032,6035],{},[34,6030,6031],{},"PHP カンファレンス 2025",[34,6033,6034],{},"Vue.js v-tokyo Meetup #23",[34,6036,6037],{},"Vue Fes Japan 2025",[15,6039,6040],{},"参加者側からコアスタッフ側に回ると、見える景色が全然違いました。当日までの準備の大変さ、当日のトラブル対応、そして参加者の「楽しかった」という声を聞いたときの達成感。イベント運営の裏側を知れたのは貴重な経験でした。",[15,6042,6043],{},"来年も Vue Fes Japan 2026 に関わっていきたいと思います。",[15,6045,6046],{},"OSS への貢献も増やすことができた一年でした。小さな貢献でも、自分のコードが誰かの役に立っていると思うとモチベーションになります。",[11,6048,6049],{"id":6049},"プライベート",[15,6051,6052],{},"6月に結婚しました。独身時代に比べて考えることは増えましたが、二人で将来を考えられるのは心強いです（責任感も増えました）。",[15,6054,6055],{},"結婚を機にマンションも購入しました。物件は絶賛建設中で、来年後半に引越予定です。住宅ローンや金利、保険など、これまで縁のなかった分野を学ぶ良い機会になりました。「変動か固定か」で何日も悩みましたが、コミュニティで相談に乗ってもらえたのも助かりました。",[15,6057,6058],{},"久しぶりに実家にも帰省しました。甥っ子が増えていて驚きました。来年はお年玉のポチ袋がさらに増えそうです。",[15,6060,6061],{},"猫用の自動トイレも導入しました。毎日の掃除がとても楽になって、もっと早く買えばよかったと思っています。",[11,6063,6064],{"id":6064},"来年に向けて",[15,6066,6067],{},"来年は結婚式を控えており、準備に追われそうです。仕事面では新しい環境でのチャレンジが始まります。新居での生活も楽しみです。",[15,6069,6070],{},"2025年を一言で表すなら「怒涛」でした。大変なことも多かったけれど、振り返ってみると全部良い経験だったと思えます。来年も忙しくなりそうですが、今年よりも少しだけ上手に周りを頼りながら、一年を過ごしていきたいです。",{"title":108,"searchDepth":122,"depth":122,"links":6072},[6073,6074,6075,6076,6077],{"id":13,"depth":122,"text":13},{"id":6007,"depth":122,"text":6007},{"id":6022,"depth":122,"text":6023},{"id":6049,"depth":122,"text":6049},{"id":6064,"depth":122,"text":6064},"2025-12-30","転職、結婚、マンション購入。人生の転機が重なった2025年を振り返ります。",{"tags":6081},[6082],"振り返り","\u002Fblog\u002F2025-review",{"title":5997,"description":6079},"blog\u002F2025-review","tTta0iU3JizzqVrMIiR4z0lfe9fn2-sIkhatR4xg5zs",{"id":6088,"title":6089,"body":6090,"date":6394,"description":6395,"extension":962,"meta":6396,"navigation":146,"path":6401,"seo":6402,"stem":6403,"__hash__":6404},"blog\u002Fblog\u002Fvue-fes-japan-2025.md","Vue Fes Japan 2025 に登壇しました！",{"type":8,"value":6091,"toc":6383},[6092,6094,6105,6108,6114,6117,6120,6131,6134,6138,6142,6145,6148,6151,6154,6344,6347,6350,6356,6359,6362,6365,6368,6371,6374,6377,6380],[11,6093,13],{"id":13},[15,6095,6096,6097,6104],{},"この記事は、📅 ",[27,6098,6099],{},[759,6100,6103],{"href":6101,"rel":6102},"https:\u002F\u002Fqiita.com\u002Fadvent-calendar\u002F2025\u002Fvue",[763],"Vue Advent Calendar 2025"," の 18 日目の記事です。",[15,6106,6107],{},"登壇ブログを書くのが遅くなってしまいましたが、2025 年 10 月 25 日に開催された Vue Fes Japan 2025 で登壇しました。タイトルは「Nuxt 4 の Singleton Data Fetching Layer で何が変わるのか」です。",[15,6109,6110],{},[759,6111,6112],{"href":6112,"rel":6113},"https:\u002F\u002Fvuefes.jp\u002F2025\u002Fspeaker\u002FNaokiHaba",[763],[15,6115,6116],{},"技術的な部分は Speaker Deck の資料をご覧いただく方が良いかと思いますので、この記事では登壇までの準備や当日の様子を中心にお伝えします。",[83,6118,6119],{"id":6119},"なぜこのテーマを選んだのか",[15,6121,6122,6123,6126,6127,6130],{},"Singleton Data Fetching Layer は Nuxt 3.17 で導入された機能で、",[90,6124,6125],{},"useAsyncData"," \u002F ",[90,6128,6129],{},"useFetch"," のデータ取得の仕組みが大きく改善されました。私自身、業務で複数コンポーネントから同じ API を呼び出す場面で fetcher の重複実行に悩まされた経験があり、この改善がどれほど嬉しいものか身をもって知っていました。",[15,6132,6133],{},"「自分が困っていたことが解決された」という実体験をもとに話せるテーマであり、かつ Nuxt 4 に向けた重要な変更点でもあるため、5 分間の LT で伝える価値があると考えました。",[11,6135,6137],{"id":6136},"cfp-応募から登壇まで","CFP 応募から登壇まで",[83,6139,6141],{"id":6140},"cfp-を書く","CFP を書く",[15,6143,6144],{},"Dan Abramov さんのブログ記事を参考にしつつ、\"なぜこの話をするのか\"・\"聞き手にとってどんな価値があるのか\" を意識して CFP を書きました。",[15,6146,6147],{},"特に意識したのは「問題提起 → 解決策 → 持ち帰れる具体的な知識」という流れです。単に「こんな機能があります」ではなく、「こんな問題がありました → こう解決されました」という構成を意識しました。聞き手が自分のプロジェクトでも活かせる内容になるといいなぁと考えていました。",[83,6149,6150],{"id":6150},"アウトラインの作成",[15,6152,6153],{},"最終形はこんな感じですが、最初は色々追加しては「絶対 5 分無理やろ…」という感じで削っては調整を繰り返しました。",[103,6155,6158],{"className":6156,"code":6157,"language":962,"meta":108,"style":108},"language-md shiki shiki-themes github-light github-dark","- intro\n  - hi, I'm Naoki Haba\n- problems（問題提起）\n  - 同一 `key` での `useAsyncData\u002FuseFetch` はデータを共有する仕組み\n  - Nuxt 3.17以前：データは共有されるが、fetcherが重複実行される\n    - 複数コンポーネントで同じAPIを呼ぶECサイト等\n  - `key` には `string` だけしか指定できない\n    - Reactiveな値を指定できない\n- crossroads（転換点）\n  - Nuxt 3.17で `useAsyncData\u002FuseFetch` のデータ取得が再編成された\n    - Singleton Data Fetching Layer\n- demo（実演）\n  - Nuxt 3.16以前の挙動\n    - fetcherが重複実行される\n  - Nuxt 3.17以降の挙動\n    - fetcherは一度だけ実行され、データは共有される\n    - Reactiveな値を `key` に指定できる\n- recap（まとめ）\n  - Nuxt 3.17で `useAsyncData\u002FuseFetch` のデータ取得が改善された\n- outro（締め）\n  - Thank you for listening!\n",[90,6159,6160,6167,6175,6182,6201,6208,6216,6232,6239,6246,6258,6265,6272,6279,6286,6293,6300,6312,6319,6330,6337],{"__ignoreMap":108},[112,6161,6162,6164],{"class":114,"line":115},[112,6163,1395],{"class":222},[112,6165,6166],{"class":129}," intro\n",[112,6168,6169,6172],{"class":114,"line":122},[112,6170,6171],{"class":222},"  -",[112,6173,6174],{"class":129}," hi, I'm Naoki Haba\n",[112,6176,6177,6179],{"class":114,"line":143},[112,6178,1395],{"class":222},[112,6180,6181],{"class":129}," problems（問題提起）\n",[112,6183,6184,6186,6189,6192,6195,6198],{"class":114,"line":150},[112,6185,6171],{"class":222},[112,6187,6188],{"class":129}," 同一 ",[112,6190,6191],{"class":156},"`key`",[112,6193,6194],{"class":129}," での ",[112,6196,6197],{"class":156},"`useAsyncData\u002FuseFetch`",[112,6199,6200],{"class":129}," はデータを共有する仕組み\n",[112,6202,6203,6205],{"class":114,"line":170},[112,6204,6171],{"class":222},[112,6206,6207],{"class":129}," Nuxt 3.17以前：データは共有されるが、fetcherが重複実行される\n",[112,6209,6210,6213],{"class":114,"line":182},[112,6211,6212],{"class":222},"    -",[112,6214,6215],{"class":129}," 複数コンポーネントで同じAPIを呼ぶECサイト等\n",[112,6217,6218,6220,6223,6226,6229],{"class":114,"line":193},[112,6219,6171],{"class":222},[112,6221,6222],{"class":156}," `key`",[112,6224,6225],{"class":129}," には ",[112,6227,6228],{"class":156},"`string`",[112,6230,6231],{"class":129}," だけしか指定できない\n",[112,6233,6234,6236],{"class":114,"line":205},[112,6235,6212],{"class":222},[112,6237,6238],{"class":129}," Reactiveな値を指定できない\n",[112,6240,6241,6243],{"class":114,"line":241},[112,6242,1395],{"class":222},[112,6244,6245],{"class":129}," crossroads（転換点）\n",[112,6247,6248,6250,6253,6255],{"class":114,"line":247},[112,6249,6171],{"class":222},[112,6251,6252],{"class":129}," Nuxt 3.17で ",[112,6254,6197],{"class":156},[112,6256,6257],{"class":129}," のデータ取得が再編成された\n",[112,6259,6260,6262],{"class":114,"line":351},[112,6261,6212],{"class":222},[112,6263,6264],{"class":129}," Singleton Data Fetching Layer\n",[112,6266,6267,6269],{"class":114,"line":361},[112,6268,1395],{"class":222},[112,6270,6271],{"class":129}," demo（実演）\n",[112,6273,6274,6276],{"class":114,"line":367},[112,6275,6171],{"class":222},[112,6277,6278],{"class":129}," Nuxt 3.16以前の挙動\n",[112,6280,6281,6283],{"class":114,"line":373},[112,6282,6212],{"class":222},[112,6284,6285],{"class":129}," fetcherが重複実行される\n",[112,6287,6288,6290],{"class":114,"line":379},[112,6289,6171],{"class":222},[112,6291,6292],{"class":129}," Nuxt 3.17以降の挙動\n",[112,6294,6295,6297],{"class":114,"line":1302},[112,6296,6212],{"class":222},[112,6298,6299],{"class":129}," fetcherは一度だけ実行され、データは共有される\n",[112,6301,6302,6304,6307,6309],{"class":114,"line":1502},[112,6303,6212],{"class":222},[112,6305,6306],{"class":129}," Reactiveな値を ",[112,6308,6191],{"class":156},[112,6310,6311],{"class":129}," に指定できる\n",[112,6313,6314,6316],{"class":114,"line":1507},[112,6315,1395],{"class":222},[112,6317,6318],{"class":129}," recap（まとめ）\n",[112,6320,6321,6323,6325,6327],{"class":114,"line":1512},[112,6322,6171],{"class":222},[112,6324,6252],{"class":129},[112,6326,6197],{"class":156},[112,6328,6329],{"class":129}," のデータ取得が改善された\n",[112,6331,6332,6334],{"class":114,"line":1518},[112,6333,1395],{"class":222},[112,6335,6336],{"class":129}," outro（締め）\n",[112,6338,6339,6341],{"class":114,"line":1524},[112,6340,6171],{"class":222},[112,6342,6343],{"class":129}," Thank you for listening!\n",[15,6345,6346],{},"5 分間の LT ということで、伝えたいことを絞り込むのに苦労しました。最初は全部詰め込みたくなります。",[15,6348,6349],{},"そんなときに参考にしたのが Dan Abramov さんの記事です。",[6351,6352,6353],"blockquote",{},[15,6354,6355],{},"What is the one thing that you want people to take away from your talk? I try to formulate it as a sentence early on. This idea shouldn't be longer than a dozen words. People will forget most of what you say so you need to pick carefully what you want to stick. It's the seed you want to plant in their heads.",[15,6357,6358],{},"「聴衆に持ち帰ってほしい 1 つのことは何か」を意識して、Singleton Data Fetching Layer の「重複実行の解消」と「Reactive な key のサポート」に焦点を絞りました。",[11,6360,6361],{"id":6361},"当日の様子",[15,6363,6364],{},"今年はコアスタッフとしてスポンサーチームのリーダーを務めながら、学生支援プログラムの司会進行と自分の登壇準備を並行して行いました。カンファレンスを「作る側」として関わるのは初めてで、貴重な経験となりました。",[15,6366,6367],{},"登壇は緊張しましたが、リハーサルを重ねたおかげで無事に時間内に収められました。他のスピーカーの方々のセッションも刺激的で、学びの多い 1 日でした。",[11,6369,6370],{"id":6370},"おわりに",[15,6372,6373],{},"コアスタッフと登壇者、両方の立場で Vue Fes Japan 2025 に関われたのは貴重な経験でした。準備は大変でしたが、登壇後にいただいた反応がとても嬉しかったです。",[15,6375,6376],{},"改めて、一緒に準備を進めてくれたスタッフの皆さん、当日参加してくださった皆さん、そして Vue.js コミュニティに感謝します。来年もまた会場でお会いできることを楽しみにしています。",[15,6378,6379],{},"明日は @hiro_xre さんによる「スタイルガイドを意識して Vue.js を書く」が予定されています。お楽しみに！",[944,6381,6382],{},"html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":108,"searchDepth":122,"depth":122,"links":6384},[6385,6388,6392,6393],{"id":13,"depth":122,"text":13,"children":6386},[6387],{"id":6119,"depth":143,"text":6119},{"id":6136,"depth":122,"text":6137,"children":6389},[6390,6391],{"id":6140,"depth":143,"text":6141},{"id":6150,"depth":143,"text":6150},{"id":6361,"depth":122,"text":6361},{"id":6370,"depth":122,"text":6370},"2025-12-18","Vue Fes Japan 2025で「Nuxt 4 の Singleton Data Fetching Layer で何が変わるのか」というタイトルで登壇しました。CFP応募から当日までの振り返りです。",{"tags":6397},[6398,6399,6400],"vue","nuxt","conference","\u002Fblog\u002Fvue-fes-japan-2025",{"title":6089,"description":6395},"blog\u002Fvue-fes-japan-2025","o2309rxfQ6i2ebxY8R1rNX2l5jtkcoKlwTw0HY-RkHM",{"id":6406,"title":6407,"body":6408,"date":6769,"description":6770,"extension":962,"meta":6771,"navigation":146,"path":6775,"seo":6776,"stem":6777,"__hash__":6778},"blog\u002Fblog\u002Fvuejs-language-tools-pr.md","vuejs\u002Flanguage-tools の PR を読み解く：DiffWindow のハイライト問題",{"type":8,"value":6409,"toc":6759},[6410,6412,6420,6423,6430,6436,6442,6446,6449,6452,6457,6463,6471,6478,6494,6498,6504,6507,6510,6524,6527,6530,6534,6541,6622,6633,6647,6652,6656,6663,6732,6734,6737,6740,6742,6756],[11,6411,13],{"id":13},[15,6413,6096,6414,6419],{},[27,6415,6416],{},[759,6417,6103],{"href":6101,"rel":6418},[763]," の 8 日目の記事です。",[15,6421,6422],{},"Visual Studio Code（以下、VS Code）で Vue.js を開発する際に便利な拡張機能である Vue (Official) を利用される場合が多いのではないでしょうか。",[15,6424,6425,6426,6429],{},"本日リリースされた ",[90,6427,6428],{},"v3.1.7"," で気になる修正があったので、再現確認とコミットを追ってみました。",[15,6431,6432],{},[759,6433,6434],{"href":6434,"rel":6435},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Flanguage-tools\u002Freleases\u002Ftag\u002Fv3.1.7",[763],[15,6437,6438],{},[759,6439,6440],{"href":6440,"rel":6441},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Flanguage-tools\u002Fpull\u002F5811",[763],[11,6443,6445],{"id":6444},"vuejslanguage-tools-とは","vuejs\u002Flanguage-tools とは",[15,6447,6448],{},"まず、vuejs\u002Flanguage-tools について簡単に紹介します。",[15,6450,6451],{},"Vue.js の公式ドキュメントでは、IDE サポートについて以下のように説明されています。",[6351,6453,6454],{},[15,6455,6456],{},"The recommended IDE setup is VS Code + the Vue - Official extension (previously Volar). The extension provides syntax highlighting, TypeScript support, and intellisense for template expressions and component props.",[15,6458,6459],{},[759,6460,6461],{"href":6461,"rel":6462},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fscaling-up\u002Ftooling.html#ide-support",[763],[15,6464,6465,6466,397],{},"この Vue - Official 拡張機能の中核となるのが ",[759,6467,6470],{"href":6468,"rel":6469},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Flanguage-tools",[763],"vuejs\u002Flanguage-tools",[15,6472,6473,6474,6477],{},"以下の図のように、複数のパッケージで構成されています。今回の修正は ",[90,6475,6476],{},"@vue\u002Flanguage-core"," に関するものです。",[15,6479,6480,6485],{},[6481,6482],"img",{"alt":6483,"src":6484},"vuejs\u002Flanguage-tools のアーキテクチャ","https:\u002F\u002Fi.gyazo.com\u002F28ab68ee0e3ce23708c51c8522e7050c.png",[6486,6487,6488,6493],"em",{},[759,6489,6492],{"href":6490,"rel":6491},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Flanguage-tools\u002Fblob\u002Fmaster\u002FREADME.md#community-integration",[763],"Community Integration - vuejs\u002Flanguage-tools"," より",[11,6495,6497],{"id":6496},"問題git-差分表示で色がおかしくなる","問題：Git 差分表示で色がおかしくなる",[15,6499,6500],{},[759,6501,6502],{"href":6502,"rel":6503},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Flanguage-tools\u002Fissues\u002F5572",[763],[15,6505,6506],{},"Git の差分表示（DiffWindow）で Vue ファイルを開くと、Semantic Highlighting が正しく適用されず、シンタックスハイライトの色が壊れてしまう現象が発生していました。",[15,6508,6509],{},"手元でも再現確認を行ったところ再現しました。参考までに環境は以下の通りです。",[31,6511,6512,6515,6518,6521],{},[34,6513,6514],{},"VS Code: 1.106.3 (Universal)",[34,6516,6517],{},"Vue - Official (vue.volar): 3.0.7 より前のバージョン",[34,6519,6520],{},"Vue: 3.5.25",[34,6522,6523],{},"vue-tsc: 3.0.2",[15,6525,6526],{},"PR を見るだけだと理解が深まらないので、フォークして実際のコードをいじりながらテストを回していくことにしました。",[15,6528,6529],{},"以下は検証に利用したコードなので、興味ある方は手元でも試して読んでみてください。",[11,6531,6533],{"id":6532},"原因キャッシュキーの競合","原因：キャッシュキーの競合",[15,6535,6536,6537,6540],{},"原因は、仮想コードのキャッシュキーに ",[90,6538,6539],{},"fileName","（ファイルパスのみ）を使用していたことにあります。",[103,6542,6544],{"className":105,"code":6543,"language":107,"meta":108,"style":108},"\u002F\u002F 変更前：fileName をキーに使用\nfileRegistry.get(fileName)\nfileRegistry.set(fileName, code)\n\n\u002F\u002F disposeVirtualCode も同様\ndisposeVirtualCode(scriptId) {\n    const fileName = asFileName(scriptId);\n    fileRegistry.delete(fileName);\n}\n\u002F\u002F キー例: '\u002Fpath\u002Fto\u002FApp.vue'\n",[90,6545,6546,6551,6561,6570,6574,6579,6587,6602,6613,6617],{"__ignoreMap":108},[112,6547,6548],{"class":114,"line":115},[112,6549,6550],{"class":118},"\u002F\u002F 変更前：fileName をキーに使用\n",[112,6552,6553,6556,6558],{"class":114,"line":122},[112,6554,6555],{"class":129},"fileRegistry.",[112,6557,3344],{"class":163},[112,6559,6560],{"class":129},"(fileName)\n",[112,6562,6563,6565,6567],{"class":114,"line":143},[112,6564,6555],{"class":129},[112,6566,3842],{"class":163},[112,6568,6569],{"class":129},"(fileName, code)\n",[112,6571,6572],{"class":114,"line":150},[112,6573,147],{"emptyLinePlaceholder":146},[112,6575,6576],{"class":114,"line":170},[112,6577,6578],{"class":118},"\u002F\u002F disposeVirtualCode も同様\n",[112,6580,6581,6584],{"class":114,"line":182},[112,6582,6583],{"class":163},"disposeVirtualCode",[112,6585,6586],{"class":129},"(scriptId) {\n",[112,6588,6589,6591,6594,6596,6599],{"class":114,"line":193},[112,6590,3472],{"class":125},[112,6592,6593],{"class":156}," fileName",[112,6595,160],{"class":125},[112,6597,6598],{"class":163}," asFileName",[112,6600,6601],{"class":129},"(scriptId);\n",[112,6603,6604,6607,6610],{"class":114,"line":205},[112,6605,6606],{"class":129},"    fileRegistry.",[112,6608,6609],{"class":163},"delete",[112,6611,6612],{"class":129},"(fileName);\n",[112,6614,6615],{"class":114,"line":241},[112,6616,1452],{"class":129},[112,6618,6619],{"class":114,"line":247},[112,6620,6621],{"class":118},"\u002F\u002F キー例: '\u002Fpath\u002Fto\u002FApp.vue'\n",[15,6623,6624,6625,6628,6629,6632],{},"通常のファイルは ",[90,6626,6627],{},"file:\u002F\u002F"," スキームでアクセスされますが、Git の差分表示では ",[90,6630,6631],{},"git:\u002F\u002F"," スキームが使われます。",[31,6634,6635,6641],{},[34,6636,6637,6638],{},"通常: ",[90,6639,6640],{},"file:\u002F\u002F\u002Fpath\u002Fto\u002FApp.vue",[34,6642,6643,6644],{},"Git差分: ",[90,6645,6646],{},"git:\u002Fpath\u002Fto\u002FApp.vue",[15,6648,6649,6651],{},[90,6650,6539],{}," だけをキーにすると、両者が同じキャッシュエントリを参照してしまい、Git diff バッファが通常ファイルのスナップショットを上書きしてしまいます。",[83,6653,6655],{"id":6654},"解決策scriptid-によるキャッシュ分離","解決策：scriptId によるキャッシュ分離",[15,6657,6658,6659,6662],{},"PR #5811 では、キャッシュキーを ",[90,6660,6661],{},"scriptId","（URI 全体）に変更することで、スキームごとに独立したキャッシュを保持するようにしました。",[103,6664,6666],{"className":105,"code":6665,"language":107,"meta":108,"style":108},"\u002F\u002F 変更後：scriptId をキーに使用\nfileRegistry.get(String(scriptId))\nfileRegistry.set(String(scriptId), code)\n\ndisposeVirtualCode(scriptId) {\n    fileRegistry.delete(String(scriptId));\n}\n\u002F\u002F キー例: 'file:\u002F\u002F\u002Fpath\u002Fto\u002FApp.vue' または 'git:\u002Fpath\u002Fto\u002FApp.vue'\n",[90,6667,6668,6673,6687,6700,6704,6710,6723,6727],{"__ignoreMap":108},[112,6669,6670],{"class":114,"line":115},[112,6671,6672],{"class":118},"\u002F\u002F 変更後：scriptId をキーに使用\n",[112,6674,6675,6677,6679,6681,6684],{"class":114,"line":122},[112,6676,6555],{"class":129},[112,6678,3344],{"class":163},[112,6680,425],{"class":129},[112,6682,6683],{"class":163},"String",[112,6685,6686],{"class":129},"(scriptId))\n",[112,6688,6689,6691,6693,6695,6697],{"class":114,"line":143},[112,6690,6555],{"class":129},[112,6692,3842],{"class":163},[112,6694,425],{"class":129},[112,6696,6683],{"class":163},[112,6698,6699],{"class":129},"(scriptId), code)\n",[112,6701,6702],{"class":114,"line":150},[112,6703,147],{"emptyLinePlaceholder":146},[112,6705,6706,6708],{"class":114,"line":170},[112,6707,6583],{"class":163},[112,6709,6586],{"class":129},[112,6711,6712,6714,6716,6718,6720],{"class":114,"line":182},[112,6713,6606],{"class":129},[112,6715,6609],{"class":163},[112,6717,425],{"class":129},[112,6719,6683],{"class":163},[112,6721,6722],{"class":129},"(scriptId));\n",[112,6724,6725],{"class":114,"line":193},[112,6726,1452],{"class":129},[112,6728,6729],{"class":114,"line":205},[112,6730,6731],{"class":118},"\u002F\u002F キー例: 'file:\u002F\u002F\u002Fpath\u002Fto\u002FApp.vue' または 'git:\u002Fpath\u002Fto\u002FApp.vue'\n",[11,6733,6370],{"id":6370},[15,6735,6736],{},"日々お世話になっている Vue - Official 拡張機能の中核である vuejs\u002Flanguage-tools の改善により、Git 差分表示での Semantic Highlighting 問題が解決されました。",[15,6738,6739],{},"VS Code で Vue.js を開発されている方は、ぜひ最新バージョン（v3.1.7 以降）へアップデートしてみてください。",[11,6741,2930],{"id":2930},[31,6743,6744,6750],{},[34,6745,6746],{},[759,6747,6749],{"href":6440,"rel":6748},[763],"PR #5811: feat(language-core): cache virtual code by scriptId",[34,6751,6752],{},[759,6753,6755],{"href":6502,"rel":6754},[763],"Issue #5572: Different font colors in DiffWindow",[944,6757,6758],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":108,"searchDepth":122,"depth":122,"links":6760},[6761,6762,6763,6764,6767,6768],{"id":13,"depth":122,"text":13},{"id":6444,"depth":122,"text":6445},{"id":6496,"depth":122,"text":6497},{"id":6532,"depth":122,"text":6533,"children":6765},[6766],{"id":6654,"depth":143,"text":6655},{"id":6370,"depth":122,"text":6370},{"id":2930,"depth":122,"text":2930},"2025-12-08","Vue - Official 拡張機能 v3.1.7 で修正された Git 差分表示のハイライト問題について、PR を追いながら原因と解決策を解説します。",{"tags":6772},[6398,6773,6774],"vscode","oss","\u002Fblog\u002Fvuejs-language-tools-pr",{"title":6407,"description":6770},"blog\u002Fvuejs-language-tools-pr","NTmMf3OFkOwbgGcZOAut8QCWwVMuDi0uJ0VLZs7FyX0",{"id":6780,"title":6781,"body":6782,"date":7050,"description":7051,"extension":962,"meta":7052,"navigation":146,"path":7057,"seo":7058,"stem":7059,"__hash__":7060},"blog\u002Fblog\u002Fcreate-cloudflare-cli-c3.md","create-cloudflare(C3)でHonoアプリをデプロイする",{"type":8,"value":6783,"toc":7040},[6784,6788,6796,6800,6814,6818,6821,6839,6842,6873,6876,6879,6882,6904,6910,6914,6917,6920,6983,6987,7006,7009,7011,7014,7034,7037],[11,6785,6787],{"id":6786},"c3とは","C3とは",[15,6789,6790,6795],{},[759,6791,6794],{"href":6792,"rel":6793},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fpages\u002Fget-started\u002Fc3\u002F",[763],"create-cloudflare(C3)","は、Cloudflare公式のプロジェクト作成CLIツールです。",[15,6797,6798],{},[27,6799,2608],{},[31,6801,6802,6805,6808,6811],{},[34,6803,6804],{},"対話形式でプロジェクトをセットアップ",[34,6806,6807],{},"Hono、Nuxt、React、Vue、Svelteなど主要フレームワークをサポート",[34,6809,6810],{},"GitHub連携による自動デプロイに対応",[34,6812,6813],{},"Cloudflare WorkersまたはPagesにすぐにデプロイ可能",[11,6815,6817],{"id":6816},"honoプロジェクトのセットアップ","Honoプロジェクトのセットアップ",[15,6819,6820],{},"C3を使ってHonoプロジェクトを作成します。",[103,6822,6826],{"className":6823,"code":6824,"language":6825,"meta":108,"style":108},"language-shell shiki shiki-themes github-light github-dark","npm create cloudflare@latest\n","shell",[90,6827,6828],{"__ignoreMap":108},[112,6829,6830,6833,6836],{"class":114,"line":115},[112,6831,6832],{"class":163},"npm",[112,6834,6835],{"class":136}," create",[112,6837,6838],{"class":136}," cloudflare@latest\n",[15,6840,6841],{},"対話形式で以下の質問に答えます。",[4108,6843,6844,6850,6856,6862,6868],{},[34,6845,6846,6849],{},[27,6847,6848],{},"ディレクトリ名",": プロジェクトの作成先",[34,6851,6852,6855],{},[27,6853,6854],{},"アプリケーションタイプ",": \"Website or web app\" を選択",[34,6857,6858,6861],{},[27,6859,6860],{},"フレームワーク",": \"Hono\" を選択",[34,6863,6864,6867],{},[27,6865,6866],{},"Gitバージョン管理",": \"yes\" を選択",[34,6869,6870,6872],{},[27,6871,69],{},": GitHub連携を使うため \"no\" を選択",[15,6874,6875],{},"これだけでHonoプロジェクトのセットアップが完了します。",[11,6877,6878],{"id":6878},"ローカルでの動作確認",[15,6880,6881],{},"開発サーバーを起動して動作を確認します。",[103,6883,6885],{"className":6823,"code":6884,"language":6825,"meta":108,"style":108},"cd hono-cloudflare-c3-sample\nnpm run dev\n",[90,6886,6887,6895],{"__ignoreMap":108},[112,6888,6889,6892],{"class":114,"line":115},[112,6890,6891],{"class":156},"cd",[112,6893,6894],{"class":136}," hono-cloudflare-c3-sample\n",[112,6896,6897,6899,6902],{"class":114,"line":122},[112,6898,6832],{"class":163},[112,6900,6901],{"class":136}," run",[112,6903,787],{"class":136},[15,6905,6906,6909],{},[90,6907,6908],{},"http:\u002F\u002Flocalhost:8787"," にアクセスすると、Hello World! が表示されます。",[11,6911,6913],{"id":6912},"github連携によるデプロイ","GitHub連携によるデプロイ",[83,6915,6916],{"id":6916},"リポジトリへのプッシュ",[15,6918,6919],{},"作成したプロジェクトをGitHubにプッシュします。",[103,6921,6923],{"className":6823,"code":6922,"language":6825,"meta":108,"style":108},"git remote add origin https:\u002F\u002Fgithub.com\u002F\u003Cusername>\u002F\u003Crepository>\ngit push -u origin main\n",[90,6924,6925,6968],{"__ignoreMap":108},[112,6926,6927,6930,6933,6936,6939,6942,6945,6948,6951,6954,6957,6959,6962,6965],{"class":114,"line":115},[112,6928,6929],{"class":163},"git",[112,6931,6932],{"class":136}," remote",[112,6934,6935],{"class":136}," add",[112,6937,6938],{"class":136}," origin",[112,6940,6941],{"class":136}," https:\u002F\u002Fgithub.com\u002F",[112,6943,6944],{"class":125},"\u003C",[112,6946,6947],{"class":136},"usernam",[112,6949,6950],{"class":129},"e",[112,6952,6953],{"class":125},">",[112,6955,6956],{"class":136},"\u002F",[112,6958,6944],{"class":125},[112,6960,6961],{"class":136},"repositor",[112,6963,6964],{"class":129},"y",[112,6966,6967],{"class":125},">\n",[112,6969,6970,6972,6975,6978,6980],{"class":114,"line":122},[112,6971,6929],{"class":163},[112,6973,6974],{"class":136}," push",[112,6976,6977],{"class":156}," -u",[112,6979,6938],{"class":136},[112,6981,6982],{"class":136}," main\n",[83,6984,6986],{"id":6985},"cloudflare-pagesでの設定","Cloudflare Pagesでの設定",[4108,6988,6989,6997,7000,7003],{},[34,6990,6991,6996],{},[759,6992,6995],{"href":6993,"rel":6994},"https:\u002F\u002Fdash.cloudflare.com\u002F",[763],"Cloudflare Pagesダッシュボード","にアクセス",[34,6998,6999],{},"\"Workers & Pages\" → \"Create application\" → \"Pages\" → \"Connect to Git\"",[34,7001,7002],{},"GitHubリポジトリを選択",[34,7004,7005],{},"ビルド設定はC3が自動で設定済みなので、そのままデプロイ",[15,7007,7008],{},"これでGitHubへのプッシュのたびに自動デプロイされるようになります。",[11,7010,919],{"id":919},[15,7012,7013],{},"C3を使うことで、以下のメリットがあります。",[31,7015,7016,7022,7028],{},[34,7017,7018,7021],{},[27,7019,7020],{},"セットアップが簡単"," 対話形式で必要な設定を自動生成",[34,7023,7024,7027],{},[27,7025,7026],{},"即座にデプロイ可能"," Wranglerの設定がすでに完了",[34,7029,7030,7033],{},[27,7031,7032],{},"CI\u002FCD対応"," GitHub連携で自動デプロイを実現",[15,7035,7036],{},"Cloudflare WorkersでHonoアプリを試したい方におすすめのツールです。",[944,7038,7039],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":108,"searchDepth":122,"depth":122,"links":7041},[7042,7043,7044,7045,7049],{"id":6786,"depth":122,"text":6787},{"id":6816,"depth":122,"text":6817},{"id":6878,"depth":122,"text":6878},{"id":6912,"depth":122,"text":6913,"children":7046},[7047,7048],{"id":6916,"depth":143,"text":6916},{"id":6985,"depth":143,"text":6986},{"id":919,"depth":122,"text":919},"2023-12-27","Cloudflareの公式CLIツールC3を使って、HonoアプリケーションをCloudflare Workersにセットアップ・デプロイする手順を解説します。",{"tags":7053},[7054,7055,7056],"cloudflare","hono","deployment","\u002Fblog\u002Fcreate-cloudflare-cli-c3",{"title":6781,"description":7051},"blog\u002Fcreate-cloudflare-cli-c3","AsgPViDDV5wJAqHwlMA1GqIwEIX6PGI7dpnZwRTmjjI",{"id":7062,"title":7063,"body":7064,"date":7706,"description":7707,"extension":962,"meta":7708,"navigation":146,"path":7711,"seo":7712,"stem":7713,"__hash__":7714},"blog\u002Fblog\u002Fterraform-workspace-management.md","Terraform workspaceで個人ごとのAWSリソースを効率管理",{"type":8,"value":7065,"toc":7686},[7066,7068,7071,7074,7077,7080,7089,7092,7095,7117,7121,7177,7180,7183,7242,7249,7253,7256,7259,7287,7358,7361,7364,7379,7421,7503,7506,7509,7555,7558,7588,7591,7598,7636,7639,7664,7666,7669,7680,7683],[11,7067,13],{"id":13},[15,7069,7070],{},"Terraformのworkspace機能を使うと、同じ定義ファイルで複数の独立した環境を管理できます。",[15,7072,7073],{},"個人ごとに定義ファイルを分ける運用は、1〜2名なら問題ありませんが、10名規模になると管理が煩雑になります。workspaceを使えば、この問題を解決できます。",[11,7075,7076],{"id":7076},"workspace機能とは",[15,7078,7079],{},"workspaceは、同じTerraform定義で異なる状態ファイル（tfstate）を管理する機能です。各workspaceは独立したリソースセットを持つため、個人開発環境の分離に最適です。",[15,7081,7082,7083,7088],{},"詳細は",[759,7084,7087],{"href":7085,"rel":7086},"https:\u002F\u002Fdeveloper.hashicorp.com\u002Fterraform\u002Flanguage\u002Fstate\u002Fworkspaces",[763],"公式ドキュメント","を参照してください。",[11,7090,7091],{"id":7091},"環境構築",[83,7093,7094],{"id":7094},"前提条件",[31,7096,7097,7100,7109],{},[34,7098,7099],{},"AWS CLIがインストール済み",[34,7101,7102,7103,7108],{},"IAMユーザーが作成済み（",[759,7104,7107],{"href":7105,"rel":7106},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002FIAM\u002Flatest\u002FUserGuide\u002Fid_users_create.html",[763],"IAMユーザー作成手順","）",[34,7110,7111,7112,7108],{},"Terraformがインストール済み（tfenv推奨: ",[759,7113,7116],{"href":7114,"rel":7115},"https:\u002F\u002Fgithub.com\u002Ftfutils\u002Ftfenv",[763],"tfenv",[83,7118,7120],{"id":7119},"aws-cliの設定","AWS CLIの設定",[103,7122,7124],{"className":6823,"code":7123,"language":6825,"meta":108,"style":108},"aws configure --profile \u003Cprofile名>\n\n# 以下を入力\n# AWS Access Key ID: アクセスキー ID\n# AWS Secret Access Key: シークレットアクセスキー\n# Default region name: ap-northeast-1\n# Default output format: json\n",[90,7125,7126,7148,7152,7157,7162,7167,7172],{"__ignoreMap":108},[112,7127,7128,7131,7134,7137,7140,7143,7146],{"class":114,"line":115},[112,7129,7130],{"class":163},"aws",[112,7132,7133],{"class":136}," configure",[112,7135,7136],{"class":156}," --profile",[112,7138,7139],{"class":125}," \u003C",[112,7141,7142],{"class":136},"profile",[112,7144,7145],{"class":129},"名",[112,7147,6967],{"class":125},[112,7149,7150],{"class":114,"line":122},[112,7151,147],{"emptyLinePlaceholder":146},[112,7153,7154],{"class":114,"line":143},[112,7155,7156],{"class":118},"# 以下を入力\n",[112,7158,7159],{"class":114,"line":150},[112,7160,7161],{"class":118},"# AWS Access Key ID: アクセスキー ID\n",[112,7163,7164],{"class":114,"line":170},[112,7165,7166],{"class":118},"# AWS Secret Access Key: シークレットアクセスキー\n",[112,7168,7169],{"class":114,"line":182},[112,7170,7171],{"class":118},"# Default region name: ap-northeast-1\n",[112,7173,7174],{"class":114,"line":193},[112,7175,7176],{"class":118},"# Default output format: json\n",[11,7178,7179],{"id":7179},"workspaceの作成",[15,7181,7182],{},"新しいworkspaceを作成します。",[103,7184,7186],{"className":6823,"code":7185,"language":6825,"meta":108,"style":108},"terraform workspace new test\n\n# 出力例\n# Created and switched to workspace \"test\"!\n\n# workspace一覧を確認\nterraform workspace list\n#   default\n# * test\n",[90,7187,7188,7200,7204,7209,7214,7218,7223,7232,7237],{"__ignoreMap":108},[112,7189,7190,7193,7196,7198],{"class":114,"line":115},[112,7191,7192],{"class":163},"terraform",[112,7194,7195],{"class":136}," workspace",[112,7197,232],{"class":136},[112,7199,819],{"class":136},[112,7201,7202],{"class":114,"line":122},[112,7203,147],{"emptyLinePlaceholder":146},[112,7205,7206],{"class":114,"line":143},[112,7207,7208],{"class":118},"# 出力例\n",[112,7210,7211],{"class":114,"line":150},[112,7212,7213],{"class":118},"# Created and switched to workspace \"test\"!\n",[112,7215,7216],{"class":114,"line":170},[112,7217,147],{"emptyLinePlaceholder":146},[112,7219,7220],{"class":114,"line":182},[112,7221,7222],{"class":118},"# workspace一覧を確認\n",[112,7224,7225,7227,7229],{"class":114,"line":193},[112,7226,7192],{"class":163},[112,7228,7195],{"class":136},[112,7230,7231],{"class":136}," list\n",[112,7233,7234],{"class":114,"line":205},[112,7235,7236],{"class":118},"#   default\n",[112,7238,7239],{"class":114,"line":241},[112,7240,7241],{"class":118},"# * test\n",[15,7243,7244,7245,7088],{},"詳細コマンドは",[759,7246,7087],{"href":7247,"rel":7248},"https:\u002F\u002Fdeveloper.hashicorp.com\u002Fterraform\u002Fcli\u002Fcommands\u002Fworkspace",[763],[11,7250,7252],{"id":7251},"terraform定義の作成","Terraform定義の作成",[83,7254,7255],{"id":7255},"プロバイダー設定",[15,7257,7258],{},"作業ディレクトリを作成し、AWSプロバイダーを設定します。",[103,7260,7262],{"className":6823,"code":7261,"language":6825,"meta":108,"style":108},"mkdir terraform && cd terraform\ntouch main.tf\n",[90,7263,7264,7279],{"__ignoreMap":108},[112,7265,7266,7269,7272,7274,7276],{"class":114,"line":115},[112,7267,7268],{"class":163},"mkdir",[112,7270,7271],{"class":136}," terraform",[112,7273,2822],{"class":129},[112,7275,6891],{"class":156},[112,7277,7278],{"class":136}," terraform\n",[112,7280,7281,7284],{"class":114,"line":122},[112,7282,7283],{"class":163},"touch",[112,7285,7286],{"class":136}," main.tf\n",[103,7288,7291],{"className":7289,"code":7290,"language":7192,"meta":108,"style":108},"language-terraform shiki shiki-themes github-light github-dark","# main.tf\nterraform {\n  required_providers {\n    aws = {\n      source  = \"hashicorp\u002Faws\"\n      version = \"~> 4.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region  = \"ap-northeast-1\"\n  profile = \"default\"\n}\n",[90,7292,7293,7298,7303,7308,7313,7318,7323,7327,7331,7335,7339,7344,7349,7354],{"__ignoreMap":108},[112,7294,7295],{"class":114,"line":115},[112,7296,7297],{},"# main.tf\n",[112,7299,7300],{"class":114,"line":122},[112,7301,7302],{},"terraform {\n",[112,7304,7305],{"class":114,"line":143},[112,7306,7307],{},"  required_providers {\n",[112,7309,7310],{"class":114,"line":150},[112,7311,7312],{},"    aws = {\n",[112,7314,7315],{"class":114,"line":170},[112,7316,7317],{},"      source  = \"hashicorp\u002Faws\"\n",[112,7319,7320],{"class":114,"line":182},[112,7321,7322],{},"      version = \"~> 4.0\"\n",[112,7324,7325],{"class":114,"line":193},[112,7326,3118],{},[112,7328,7329],{"class":114,"line":205},[112,7330,3232],{},[112,7332,7333],{"class":114,"line":241},[112,7334,1452],{},[112,7336,7337],{"class":114,"line":247},[112,7338,147],{"emptyLinePlaceholder":146},[112,7340,7341],{"class":114,"line":351},[112,7342,7343],{},"provider \"aws\" {\n",[112,7345,7346],{"class":114,"line":361},[112,7347,7348],{},"  region  = \"ap-northeast-1\"\n",[112,7350,7351],{"class":114,"line":367},[112,7352,7353],{},"  profile = \"default\"\n",[112,7355,7356],{"class":114,"line":373},[112,7357,1452],{},[83,7359,7360],{"id":7360},"リソース定義",[15,7362,7363],{},"VPCと個人ごとのALB target groupを作成します。",[103,7365,7367],{"className":6823,"code":7366,"language":6825,"meta":108,"style":108},"touch vpc.tf alb.tf\n",[90,7368,7369],{"__ignoreMap":108},[112,7370,7371,7373,7376],{"class":114,"line":115},[112,7372,7283],{"class":163},[112,7374,7375],{"class":136}," vpc.tf",[112,7377,7378],{"class":136}," alb.tf\n",[103,7380,7382],{"className":7289,"code":7381,"language":7192,"meta":108,"style":108},"# vpc.tf\nresource \"aws_vpc\" \"test\" {\n  cidr_block = \"10.0.0.0\u002F16\"\n\n  tags = {\n    Name = \"test\"\n  }\n}\n",[90,7383,7384,7389,7394,7399,7403,7408,7413,7417],{"__ignoreMap":108},[112,7385,7386],{"class":114,"line":115},[112,7387,7388],{},"# vpc.tf\n",[112,7390,7391],{"class":114,"line":122},[112,7392,7393],{},"resource \"aws_vpc\" \"test\" {\n",[112,7395,7396],{"class":114,"line":143},[112,7397,7398],{},"  cidr_block = \"10.0.0.0\u002F16\"\n",[112,7400,7401],{"class":114,"line":150},[112,7402,147],{"emptyLinePlaceholder":146},[112,7404,7405],{"class":114,"line":170},[112,7406,7407],{},"  tags = {\n",[112,7409,7410],{"class":114,"line":182},[112,7411,7412],{},"    Name = \"test\"\n",[112,7414,7415],{"class":114,"line":193},[112,7416,3232],{},[112,7418,7419],{"class":114,"line":205},[112,7420,1452],{},[103,7422,7424],{"className":7289,"code":7423,"language":7192,"meta":108,"style":108},"# alb.tf\nvariable \"personal_name_prefix\" {\n  type        = string\n  description = \u003C\u003C-EOF\n    個人環境ごとのPrefixを指定してください。\n    指定方法：[firstname]-[lastname]\n    注意：ALBの名前は32文字以内\n  EOF\n}\n\nresource \"aws_alb_target_group\" \"test\" {\n  name     = var.personal_name_prefix\n  port     = 80\n  protocol = \"HTTP\"\n  vpc_id   = aws_vpc.test.id\n}\n",[90,7425,7426,7431,7436,7441,7446,7451,7456,7461,7466,7470,7474,7479,7484,7489,7494,7499],{"__ignoreMap":108},[112,7427,7428],{"class":114,"line":115},[112,7429,7430],{},"# alb.tf\n",[112,7432,7433],{"class":114,"line":122},[112,7434,7435],{},"variable \"personal_name_prefix\" {\n",[112,7437,7438],{"class":114,"line":143},[112,7439,7440],{},"  type        = string\n",[112,7442,7443],{"class":114,"line":150},[112,7444,7445],{},"  description = \u003C\u003C-EOF\n",[112,7447,7448],{"class":114,"line":170},[112,7449,7450],{},"    個人環境ごとのPrefixを指定してください。\n",[112,7452,7453],{"class":114,"line":182},[112,7454,7455],{},"    指定方法：[firstname]-[lastname]\n",[112,7457,7458],{"class":114,"line":193},[112,7459,7460],{},"    注意：ALBの名前は32文字以内\n",[112,7462,7463],{"class":114,"line":205},[112,7464,7465],{},"  EOF\n",[112,7467,7468],{"class":114,"line":241},[112,7469,1452],{},[112,7471,7472],{"class":114,"line":247},[112,7473,147],{"emptyLinePlaceholder":146},[112,7475,7476],{"class":114,"line":351},[112,7477,7478],{},"resource \"aws_alb_target_group\" \"test\" {\n",[112,7480,7481],{"class":114,"line":361},[112,7482,7483],{},"  name     = var.personal_name_prefix\n",[112,7485,7486],{"class":114,"line":367},[112,7487,7488],{},"  port     = 80\n",[112,7490,7491],{"class":114,"line":373},[112,7492,7493],{},"  protocol = \"HTTP\"\n",[112,7495,7496],{"class":114,"line":379},[112,7497,7498],{},"  vpc_id   = aws_vpc.test.id\n",[112,7500,7501],{"class":114,"line":1302},[112,7502,1452],{},[11,7504,7505],{"id":7505},"実行",[83,7507,7508],{"id":7508},"実行計画の確認",[103,7510,7512],{"className":6823,"code":7511,"language":6825,"meta":108,"style":108},"terraform plan -var personal_name_prefix=test\n\n# 出力例\n# Terraform will perform the following actions:\n#   # aws_alb_target_group.test will be created\n#   # aws_vpc.test will be created\n# Plan: 2 to add, 0 to change, 0 to destroy.\n",[90,7513,7514,7527,7531,7535,7540,7545,7550],{"__ignoreMap":108},[112,7515,7516,7518,7521,7524],{"class":114,"line":115},[112,7517,7192],{"class":163},[112,7519,7520],{"class":136}," plan",[112,7522,7523],{"class":156}," -var",[112,7525,7526],{"class":136}," personal_name_prefix=test\n",[112,7528,7529],{"class":114,"line":122},[112,7530,147],{"emptyLinePlaceholder":146},[112,7532,7533],{"class":114,"line":143},[112,7534,7208],{"class":118},[112,7536,7537],{"class":114,"line":150},[112,7538,7539],{"class":118},"# Terraform will perform the following actions:\n",[112,7541,7542],{"class":114,"line":170},[112,7543,7544],{"class":118},"#   # aws_alb_target_group.test will be created\n",[112,7546,7547],{"class":114,"line":182},[112,7548,7549],{"class":118},"#   # aws_vpc.test will be created\n",[112,7551,7552],{"class":114,"line":193},[112,7553,7554],{"class":118},"# Plan: 2 to add, 0 to change, 0 to destroy.\n",[83,7556,7557],{"id":7557},"リソースの作成",[103,7559,7561],{"className":6823,"code":7560,"language":6825,"meta":108,"style":108},"terraform apply -var personal_name_prefix=test\n\n# 確認後、yesを入力\n# Apply complete! Resources: 2 added, 0 changed, 0 destroyed.\n",[90,7562,7563,7574,7578,7583],{"__ignoreMap":108},[112,7564,7565,7567,7570,7572],{"class":114,"line":115},[112,7566,7192],{"class":163},[112,7568,7569],{"class":136}," apply",[112,7571,7523],{"class":156},[112,7573,7526],{"class":136},[112,7575,7576],{"class":114,"line":122},[112,7577,147],{"emptyLinePlaceholder":146},[112,7579,7580],{"class":114,"line":143},[112,7581,7582],{"class":118},"# 確認後、yesを入力\n",[112,7584,7585],{"class":114,"line":150},[112,7586,7587],{"class":118},"# Apply complete! Resources: 2 added, 0 changed, 0 destroyed.\n",[83,7589,7590],{"id":7590},"結果確認",[15,7592,7593,7594,7597],{},"各workspaceの状態ファイルは ",[90,7595,7596],{},"terraform.tfstate.d"," ディレクトリに分離されます。",[103,7599,7601],{"className":6823,"code":7600,"language":6825,"meta":108,"style":108},"terraform workspace list\n#   default\n# * test\n\nls terraform.tfstate.d\u002F\n# test\n",[90,7602,7603,7611,7615,7619,7623,7631],{"__ignoreMap":108},[112,7604,7605,7607,7609],{"class":114,"line":115},[112,7606,7192],{"class":163},[112,7608,7195],{"class":136},[112,7610,7231],{"class":136},[112,7612,7613],{"class":114,"line":122},[112,7614,7236],{"class":118},[112,7616,7617],{"class":114,"line":143},[112,7618,7241],{"class":118},[112,7620,7621],{"class":114,"line":150},[112,7622,147],{"emptyLinePlaceholder":146},[112,7624,7625,7628],{"class":114,"line":170},[112,7626,7627],{"class":163},"ls",[112,7629,7630],{"class":136}," terraform.tfstate.d\u002F\n",[112,7632,7633],{"class":114,"line":182},[112,7634,7635],{"class":118},"# test\n",[83,7637,7638],{"id":7638},"リソースの削除",[103,7640,7642],{"className":6823,"code":7641,"language":6825,"meta":108,"style":108},"terraform destroy -var personal_name_prefix=test\n\n# Destroy complete! Resources: 1 destroyed.\n",[90,7643,7644,7655,7659],{"__ignoreMap":108},[112,7645,7646,7648,7651,7653],{"class":114,"line":115},[112,7647,7192],{"class":163},[112,7649,7650],{"class":136}," destroy",[112,7652,7523],{"class":156},[112,7654,7526],{"class":136},[112,7656,7657],{"class":114,"line":122},[112,7658,147],{"emptyLinePlaceholder":146},[112,7660,7661],{"class":114,"line":143},[112,7662,7663],{"class":118},"# Destroy complete! Resources: 1 destroyed.\n",[11,7665,919],{"id":919},[15,7667,7668],{},"Terraform workspaceを使うことで、以下のメリットがあります。",[31,7670,7671,7674,7677],{},[34,7672,7673],{},"定義ファイルの重複を防ぎ、メンテナンスコストを削減",[34,7675,7676],{},"個人ごとの環境を独立して管理",[34,7678,7679],{},"チームスケールに応じた柔軟な運用",[15,7681,7682],{},"EC2やRDSなど、他のリソースにも同様のパターンを適用できます。",[944,7684,7685],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":108,"searchDepth":122,"depth":122,"links":7687},[7688,7689,7690,7694,7695,7699,7705],{"id":13,"depth":122,"text":13},{"id":7076,"depth":122,"text":7076},{"id":7091,"depth":122,"text":7091,"children":7691},[7692,7693],{"id":7094,"depth":143,"text":7094},{"id":7119,"depth":143,"text":7120},{"id":7179,"depth":122,"text":7179},{"id":7251,"depth":122,"text":7252,"children":7696},[7697,7698],{"id":7255,"depth":143,"text":7255},{"id":7360,"depth":143,"text":7360},{"id":7505,"depth":122,"text":7505,"children":7700},[7701,7702,7703,7704],{"id":7508,"depth":143,"text":7508},{"id":7557,"depth":143,"text":7557},{"id":7590,"depth":143,"text":7590},{"id":7638,"depth":143,"text":7638},{"id":919,"depth":122,"text":919},"2023-04-01","Terraform workspaceを活用すれば、10名規模のチームでも定義ファイルを分けずに個人ごとのAWSリソースを効率的に管理できます。実践的な設定例とともに解説します。",{"tags":7709},[7130,7192,7710],"infrastructure","\u002Fblog\u002Fterraform-workspace-management",{"title":7063,"description":7707},"blog\u002Fterraform-workspace-management","rtaYAetrRX7QEY_Bm8fUUxCKQxrTbrHAFUijot8UNd4",{"id":7716,"title":7717,"body":7718,"date":8494,"description":8495,"extension":962,"meta":8496,"navigation":146,"path":8502,"seo":8503,"stem":8504,"__hash__":8505},"blog\u002Fblog\u002Fnuxt2-google-tag-manager-setup.md","Nuxt.js v2でGoogle Tag Managerを導入する",{"type":8,"value":7719,"toc":8476},[7720,7722,7728,7731,7734,7802,7805,7810,7824,7827,7838,7841,7845,7858,7864,7872,7887,7891,7894,8009,8017,8021,8024,8071,8077,8081,8084,8087,8090,8199,8202,8205,8331,8335,8341,8437,8439,8448,8451,8470,8473],[11,7721,13],{"id":13},[15,7723,7724,7727],{},[90,7725,7726],{},"@nuxtjs\u002Fgoogle-tag-manager","は既に廃止されており、代替ライブラリの導入が必要です。本記事では、Nuxt.js v2でGTMを導入する方法を解説します。",[11,7729,7730],{"id":7730},"ライブラリの選定",[83,7732,7733],{"id":7733},"調査結果",[7735,7736,7737,7753],"table",{},[7738,7739,7740],"thead",{},[7741,7742,7743,7747,7750],"tr",{},[7744,7745,7746],"th",{},"ライブラリ",[7744,7748,7749],{},"状態",[7744,7751,7752],{},"理由",[7754,7755,7756,7773,7789],"tbody",{},[7741,7757,7758,7763,7766],{},[7759,7760,7761],"td",{},[90,7762,7726],{},[7759,7764,7765],{},"❌",[7759,7767,7768],{},[759,7769,7772],{"href":7770,"rel":7771},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@nuxtjs\u002Fgoogle-tag-manager",[763],"公式に廃止済み",[7741,7774,7775,7780,7782],{},[7759,7776,7777],{},[90,7778,7779],{},"nuxt-community\u002Fgtm-module",[7759,7781,7765],{},[7759,7783,7784],{},[759,7785,7788],{"href":7786,"rel":7787},"https:\u002F\u002Fgithub.com\u002Fnuxt-community\u002Fgtm-module\u002Fissues\u002F160#issuecomment-1333076308",[763],"Nuxt 3未サポート",[7741,7790,7791,7796,7799],{},[7759,7792,7793],{},[90,7794,7795],{},"@gtm-support\u002Fvue2-gtm",[7759,7797,7798],{},"✅",[7759,7800,7801],{},"Vue 2\u002F3両対応、移行容易",[83,7803,7804],{"id":7804},"採用ライブラリ",[15,7806,7807,7809],{},[90,7808,7795],{},"を採用します。理由は以下の通りです。",[31,7811,7812,7818,7821],{},[34,7813,7814,7817],{},[90,7815,7816],{},"@gtm-support\u002Fvue-gtm","（Vue 3版）と設定方法が同じ",[34,7819,7820],{},"Nuxt 3への移行が容易",[34,7822,7823],{},"アクティブにメンテナンスされている",[11,7825,7826],{"id":7826},"想定読者",[31,7828,7829,7832,7835],{},[34,7830,7831],{},"Nuxt.js v2でGTMを導入したい方",[34,7833,7834],{},"Nuxt.js v3への移行を検討している方",[34,7836,7837],{},"既存のGTMライブラリから移行したい方",[11,7839,7840],{"id":7840},"導入手順",[83,7842,7844],{"id":7843},"_1-パッケージのインストール","1. パッケージのインストール",[15,7846,7847],{},[27,7848,7849,7850,7853,7854,7857],{},"⚠️ 警告：Webpack 4以上を使用している場合は、バージョン",[90,7851,7852],{},"@1.3.0","を指定してください。",[90,7855,7856],{},"2.0.0","では以下のエラーが発生します。",[103,7859,7862],{"className":7860,"code":7861,"language":5951},[5949],"You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.\n",[90,7863,7861],{"__ignoreMap":108},[15,7865,7866,7867],{},"詳細: ",[759,7868,7871],{"href":7869,"rel":7870},"https:\u002F\u002Fgithub.com\u002Fgtm-support\u002Fvue-gtm\u002Fissues\u002F280#issuecomment-1402707099",[763],"GitHub Issue #280",[103,7873,7875],{"className":771,"code":7874,"language":773,"meta":108,"style":108},"npm install @gtm-support\u002Fvue2-gtm@1.3.0\n",[90,7876,7877],{"__ignoreMap":108},[112,7878,7879,7881,7884],{"class":114,"line":115},[112,7880,6832],{"class":163},[112,7882,7883],{"class":136}," install",[112,7885,7886],{"class":136}," @gtm-support\u002Fvue2-gtm@1.3.0\n",[83,7888,7890],{"id":7889},"_2-nuxt-pluginの作成","2. Nuxt Pluginの作成",[15,7892,7893],{},"クライアントサイドのみで動作するプラグインを作成します。",[103,7895,7897],{"className":1909,"code":7896,"language":1911,"meta":108,"style":108},"\u002F\u002F plugins\u002Fgtm.js\nimport VueGtm from '@gtm-support\u002Fvue2-gtm'\nimport Vue from 'vue'\n\nexport default () => {\n  Vue.use(VueGtm, {\n    id: 'GTM-XXXXXXX', \u002F\u002F GTMコンテナIDを設定\n    enabled: true,      \u002F\u002F 開発環境では false に設定可能\n    debug: process.env.NODE_ENV !== 'production', \u002F\u002F 開発時はデバッグモード\n  })\n}\n",[90,7898,7899,7904,7916,7928,7932,7944,7955,7968,7981,8000,8005],{"__ignoreMap":108},[112,7900,7901],{"class":114,"line":115},[112,7902,7903],{"class":118},"\u002F\u002F plugins\u002Fgtm.js\n",[112,7905,7906,7908,7911,7913],{"class":114,"line":122},[112,7907,126],{"class":125},[112,7909,7910],{"class":129}," VueGtm ",[112,7912,133],{"class":125},[112,7914,7915],{"class":136}," '@gtm-support\u002Fvue2-gtm'\n",[112,7917,7918,7920,7923,7925],{"class":114,"line":143},[112,7919,126],{"class":125},[112,7921,7922],{"class":129}," Vue ",[112,7924,133],{"class":125},[112,7926,7927],{"class":136}," 'vue'\n",[112,7929,7930],{"class":114,"line":150},[112,7931,147],{"emptyLinePlaceholder":146},[112,7933,7934,7936,7938,7940,7942],{"class":114,"line":170},[112,7935,288],{"class":125},[112,7937,291],{"class":125},[112,7939,3802],{"class":129},[112,7941,229],{"class":125},[112,7943,1294],{"class":129},[112,7945,7946,7949,7952],{"class":114,"line":182},[112,7947,7948],{"class":129},"  Vue.",[112,7950,7951],{"class":163},"use",[112,7953,7954],{"class":129},"(VueGtm, {\n",[112,7956,7957,7960,7963,7965],{"class":114,"line":193},[112,7958,7959],{"class":129},"    id: ",[112,7961,7962],{"class":136},"'GTM-XXXXXXX'",[112,7964,447],{"class":129},[112,7966,7967],{"class":118},"\u002F\u002F GTMコンテナIDを設定\n",[112,7969,7970,7973,7975,7978],{"class":114,"line":205},[112,7971,7972],{"class":129},"    enabled: ",[112,7974,4345],{"class":156},[112,7976,7977],{"class":129},",      ",[112,7979,7980],{"class":118},"\u002F\u002F 開発環境では false に設定可能\n",[112,7982,7983,7986,7989,7992,7995,7997],{"class":114,"line":241},[112,7984,7985],{"class":129},"    debug: process.env.",[112,7987,7988],{"class":156},"NODE_ENV",[112,7990,7991],{"class":125}," !==",[112,7993,7994],{"class":136}," 'production'",[112,7996,447],{"class":129},[112,7998,7999],{"class":118},"\u002F\u002F 開発時はデバッグモード\n",[112,8001,8002],{"class":114,"line":247},[112,8003,8004],{"class":129},"  })\n",[112,8006,8007],{"class":114,"line":351},[112,8008,1452],{"class":129},[15,8010,8011,8012],{},"公式ドキュメント: ",[759,8013,8016],{"href":8014,"rel":8015},"https:\u002F\u002Fnuxtjs.org\u002Fdocs\u002Fdirectory-structure\u002Fplugins\u002F",[763],"Nuxt.js Plugins Directory",[83,8018,8020],{"id":8019},"_3-nuxtconfigjsへの登録","3. nuxt.config.jsへの登録",[15,8022,8023],{},"プラグインをクライアントモードで登録します。",[103,8025,8027],{"className":1909,"code":8026,"language":1911,"meta":108,"style":108},"\u002F\u002F nuxt.config.js\nexport default {\n  plugins: [\n    { src: '~\u002Fplugins\u002Fgtm.js', mode: 'client' },\n  ],\n}\n",[90,8028,8029,8034,8042,8047,8063,8067],{"__ignoreMap":108},[112,8030,8031],{"class":114,"line":115},[112,8032,8033],{"class":118},"\u002F\u002F nuxt.config.js\n",[112,8035,8036,8038,8040],{"class":114,"line":122},[112,8037,288],{"class":125},[112,8039,291],{"class":125},[112,8041,1294],{"class":129},[112,8043,8044],{"class":114,"line":143},[112,8045,8046],{"class":129},"  plugins: [\n",[112,8048,8049,8052,8055,8058,8061],{"class":114,"line":150},[112,8050,8051],{"class":129},"    { src: ",[112,8053,8054],{"class":136},"'~\u002Fplugins\u002Fgtm.js'",[112,8056,8057],{"class":129},", mode: ",[112,8059,8060],{"class":136},"'client'",[112,8062,2478],{"class":129},[112,8064,8065],{"class":114,"line":170},[112,8066,1887],{"class":129},[112,8068,8069],{"class":114,"line":182},[112,8070,1452],{"class":129},[15,8072,8073,8076],{},[90,8074,8075],{},"mode: 'client'","を指定することで、サーバーサイドレンダリング時には読み込まれません。",[83,8078,8080],{"id":8079},"_4-gtm管理画面での設定","4. GTM管理画面での設定",[15,8082,8083],{},"以上でNuxt.js側の設定は完了です。残りの設定（タグ、トリガー、変数など）は、GTM管理画面で行います。",[11,8085,8086],{"id":8086},"カスタムイベントの送信",[15,8088,8089],{},"ページ遷移以外のイベントを送信する場合は、以下のように実装します。",[103,8091,8093],{"className":1909,"code":8092,"language":1911,"meta":108,"style":108},"\u002F\u002F コンポーネント内\nexport default {\n  methods: {\n    trackButtonClick() {\n      this.$gtm.trackEvent({\n        event: 'button_click',\n        category: 'engagement',\n        action: 'click',\n        label: 'signup_button',\n        value: 1,\n      })\n    }\n  }\n}\n",[90,8094,8095,8100,8108,8113,8120,8133,8143,8153,8162,8172,8182,8187,8191,8195],{"__ignoreMap":108},[112,8096,8097],{"class":114,"line":115},[112,8098,8099],{"class":118},"\u002F\u002F コンポーネント内\n",[112,8101,8102,8104,8106],{"class":114,"line":122},[112,8103,288],{"class":125},[112,8105,291],{"class":125},[112,8107,1294],{"class":129},[112,8109,8110],{"class":114,"line":143},[112,8111,8112],{"class":129},"  methods: {\n",[112,8114,8115,8118],{"class":114,"line":150},[112,8116,8117],{"class":163},"    trackButtonClick",[112,8119,3313],{"class":129},[112,8121,8122,8125,8128,8131],{"class":114,"line":170},[112,8123,8124],{"class":156},"      this",[112,8126,8127],{"class":129},".$gtm.",[112,8129,8130],{"class":163},"trackEvent",[112,8132,167],{"class":129},[112,8134,8135,8138,8141],{"class":114,"line":182},[112,8136,8137],{"class":129},"        event: ",[112,8139,8140],{"class":136},"'button_click'",[112,8142,179],{"class":129},[112,8144,8145,8148,8151],{"class":114,"line":193},[112,8146,8147],{"class":129},"        category: ",[112,8149,8150],{"class":136},"'engagement'",[112,8152,179],{"class":129},[112,8154,8155,8158,8160],{"class":114,"line":205},[112,8156,8157],{"class":129},"        action: ",[112,8159,3894],{"class":136},[112,8161,179],{"class":129},[112,8163,8164,8167,8170],{"class":114,"line":241},[112,8165,8166],{"class":129},"        label: ",[112,8168,8169],{"class":136},"'signup_button'",[112,8171,179],{"class":129},[112,8173,8174,8177,8180],{"class":114,"line":247},[112,8175,8176],{"class":129},"        value: ",[112,8178,8179],{"class":156},"1",[112,8181,179],{"class":129},[112,8183,8184],{"class":114,"line":351},[112,8185,8186],{"class":129},"      })\n",[112,8188,8189],{"class":114,"line":361},[112,8190,3118],{"class":129},[112,8192,8193],{"class":114,"line":367},[112,8194,3232],{"class":129},[112,8196,8197],{"class":114,"line":373},[112,8198,1452],{"class":129},[11,8200,8201],{"id":8201},"環境別の設定",[15,8203,8204],{},"開発環境とプロダクション環境で異なるGTMコンテナを使用する場合：",[103,8206,8208],{"className":1909,"code":8207,"language":1911,"meta":108,"style":108},"\u002F\u002F plugins\u002Fgtm.js\nimport VueGtm from '@gtm-support\u002Fvue2-gtm'\nimport Vue from 'vue'\n\nexport default () => {\n  const gtmId = process.env.NODE_ENV === 'production'\n    ? 'GTM-PROD-XXX'\n    : 'GTM-DEV-XXX'\n\n  Vue.use(VueGtm, {\n    id: gtmId,\n    enabled: true,\n    debug: process.env.NODE_ENV !== 'production',\n  })\n}\n",[90,8209,8210,8214,8224,8234,8238,8250,8270,8278,8286,8290,8298,8303,8311,8323,8327],{"__ignoreMap":108},[112,8211,8212],{"class":114,"line":115},[112,8213,7903],{"class":118},[112,8215,8216,8218,8220,8222],{"class":114,"line":122},[112,8217,126],{"class":125},[112,8219,7910],{"class":129},[112,8221,133],{"class":125},[112,8223,7915],{"class":136},[112,8225,8226,8228,8230,8232],{"class":114,"line":143},[112,8227,126],{"class":125},[112,8229,7922],{"class":129},[112,8231,133],{"class":125},[112,8233,7927],{"class":136},[112,8235,8236],{"class":114,"line":150},[112,8237,147],{"emptyLinePlaceholder":146},[112,8239,8240,8242,8244,8246,8248],{"class":114,"line":170},[112,8241,288],{"class":125},[112,8243,291],{"class":125},[112,8245,3802],{"class":129},[112,8247,229],{"class":125},[112,8249,1294],{"class":129},[112,8251,8252,8254,8257,8259,8262,8264,8267],{"class":114,"line":182},[112,8253,1974],{"class":125},[112,8255,8256],{"class":156}," gtmId",[112,8258,160],{"class":125},[112,8260,8261],{"class":129}," process.env.",[112,8263,7988],{"class":156},[112,8265,8266],{"class":125}," ===",[112,8268,8269],{"class":136}," 'production'\n",[112,8271,8272,8275],{"class":114,"line":193},[112,8273,8274],{"class":125},"    ?",[112,8276,8277],{"class":136}," 'GTM-PROD-XXX'\n",[112,8279,8280,8283],{"class":114,"line":205},[112,8281,8282],{"class":125},"    :",[112,8284,8285],{"class":136}," 'GTM-DEV-XXX'\n",[112,8287,8288],{"class":114,"line":241},[112,8289,147],{"emptyLinePlaceholder":146},[112,8291,8292,8294,8296],{"class":114,"line":247},[112,8293,7948],{"class":129},[112,8295,7951],{"class":163},[112,8297,7954],{"class":129},[112,8299,8300],{"class":114,"line":351},[112,8301,8302],{"class":129},"    id: gtmId,\n",[112,8304,8305,8307,8309],{"class":114,"line":361},[112,8306,7972],{"class":129},[112,8308,4345],{"class":156},[112,8310,179],{"class":129},[112,8312,8313,8315,8317,8319,8321],{"class":114,"line":367},[112,8314,7985],{"class":129},[112,8316,7988],{"class":156},[112,8318,7991],{"class":125},[112,8320,7994],{"class":136},[112,8322,179],{"class":129},[112,8324,8325],{"class":114,"line":373},[112,8326,8004],{"class":129},[112,8328,8329],{"class":114,"line":379},[112,8330,1452],{"class":129},[11,8332,8334],{"id":8333},"nuxt-3への移行","Nuxt 3への移行",[15,8336,8337,8338,8340],{},"Nuxt 3に移行する際は、",[90,8339,7816],{},"（Vue 3版）に切り替えるだけです。設定方法はほぼ同じなので、スムーズに移行できます。",[103,8342,8344],{"className":1909,"code":8343,"language":1911,"meta":108,"style":108},"\u002F\u002F Nuxt 3の場合（参考）\n\u002F\u002F plugins\u002Fgtm.client.ts\nimport VueGtm from '@gtm-support\u002Fvue-gtm'\n\nexport default defineNuxtPlugin((nuxtApp) => {\n  nuxtApp.vueApp.use(VueGtm, {\n    id: 'GTM-XXXXXXX',\n    enabled: true,\n    debug: process.env.NODE_ENV !== 'production',\n  })\n})\n",[90,8345,8346,8351,8356,8367,8371,8391,8400,8408,8416,8428,8432],{"__ignoreMap":108},[112,8347,8348],{"class":114,"line":115},[112,8349,8350],{"class":118},"\u002F\u002F Nuxt 3の場合（参考）\n",[112,8352,8353],{"class":114,"line":122},[112,8354,8355],{"class":118},"\u002F\u002F plugins\u002Fgtm.client.ts\n",[112,8357,8358,8360,8362,8364],{"class":114,"line":143},[112,8359,126],{"class":125},[112,8361,7910],{"class":129},[112,8363,133],{"class":125},[112,8365,8366],{"class":136}," '@gtm-support\u002Fvue-gtm'\n",[112,8368,8369],{"class":114,"line":150},[112,8370,147],{"emptyLinePlaceholder":146},[112,8372,8373,8375,8377,8380,8382,8385,8387,8389],{"class":114,"line":170},[112,8374,288],{"class":125},[112,8376,291],{"class":125},[112,8378,8379],{"class":163}," defineNuxtPlugin",[112,8381,219],{"class":129},[112,8383,8384],{"class":222},"nuxtApp",[112,8386,226],{"class":129},[112,8388,229],{"class":125},[112,8390,1294],{"class":129},[112,8392,8393,8396,8398],{"class":114,"line":182},[112,8394,8395],{"class":129},"  nuxtApp.vueApp.",[112,8397,7951],{"class":163},[112,8399,7954],{"class":129},[112,8401,8402,8404,8406],{"class":114,"line":193},[112,8403,7959],{"class":129},[112,8405,7962],{"class":136},[112,8407,179],{"class":129},[112,8409,8410,8412,8414],{"class":114,"line":205},[112,8411,7972],{"class":129},[112,8413,4345],{"class":156},[112,8415,179],{"class":129},[112,8417,8418,8420,8422,8424,8426],{"class":114,"line":241},[112,8419,7985],{"class":129},[112,8421,7988],{"class":156},[112,8423,7991],{"class":125},[112,8425,7994],{"class":136},[112,8427,179],{"class":129},[112,8429,8430],{"class":114,"line":247},[112,8431,8004],{"class":129},[112,8433,8434],{"class":114,"line":351},[112,8435,8436],{"class":129},"})\n",[11,8438,919],{"id":919},[15,8440,8441,8442,8444,8445,8447],{},"廃止された",[90,8443,7726],{},"の代替として、",[90,8446,7795],{},"を使った実装方法を解説しました。",[15,8449,8450],{},"このライブラリを選択するメリットは次の通りです。",[31,8452,8453,8459,8465],{},[34,8454,8455,8458],{},[27,8456,8457],{},"アクティブメンテナンス"," 継続的に更新されている",[34,8460,8461,8464],{},[27,8462,8463],{},"移行容易性"," Nuxt 3への移行がスムーズ",[34,8466,8467,8469],{},[27,8468,1134],{}," カスタムイベントの送信が容易",[15,8471,8472],{},"GTMの詳細な設定は、GTM管理画面で行うことで、コードを変更せずにタグを管理できます。",[944,8474,8475],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":108,"searchDepth":122,"depth":122,"links":8477},[8478,8479,8483,8484,8490,8491,8492,8493],{"id":13,"depth":122,"text":13},{"id":7730,"depth":122,"text":7730,"children":8480},[8481,8482],{"id":7733,"depth":143,"text":7733},{"id":7804,"depth":143,"text":7804},{"id":7826,"depth":122,"text":7826},{"id":7840,"depth":122,"text":7840,"children":8485},[8486,8487,8488,8489],{"id":7843,"depth":143,"text":7844},{"id":7889,"depth":143,"text":7890},{"id":8019,"depth":143,"text":8020},{"id":8079,"depth":143,"text":8080},{"id":8086,"depth":122,"text":8086},{"id":8201,"depth":122,"text":8201},{"id":8333,"depth":122,"text":8334},{"id":919,"depth":122,"text":919},"2023-02-04","廃止された@nuxtjs\u002Fgoogle-tag-managerの代替として、@gtm-support\u002Fvue2-gtmを使ったGTM導入方法を解説します。Nuxt 3への移行も見据えた実装例を紹介します。",{"tags":8497},[8498,8499,8500,8501],"nuxtjs","gtm","ga4","analytics","\u002Fblog\u002Fnuxt2-google-tag-manager-setup",{"title":7717,"description":8495},"blog\u002Fnuxt2-google-tag-manager-setup","n1dCcxBp6pJ5pCI_Z1IT5GoPK2j9plF8W236jMF1N2g",{"id":8507,"title":8508,"body":8509,"date":9597,"description":9598,"extension":962,"meta":9599,"navigation":146,"path":9604,"seo":9605,"stem":9606,"__hash__":9607},"blog\u002Fblog\u002Fpostgresql-ssl-connection-error.md","PostgreSQL接続エラー「SSL is not enabled」の対処方法",{"type":8,"value":8510,"toc":9568},[8511,8514,8517,8592,8595,8598,8605,8616,8619,8622,8626,8633,8663,8667,8735,8738,8766,8820,8824,8827,8922,8925,8977,8980,8987,8991,8996,9095,9099,9126,9130,9239,9242,9277,9280,9283,9294,9298,9317,9320,9363,9366,9370,9397,9403,9424,9428,9431,9445,9449,9452,9500,9502,9505,9537,9540,9542,9565],[11,8512,8513],{"id":8513},"エラーの概要",[15,8515,8516],{},"golang-migrateでPostgreSQLに接続しようとすると、以下のエラーが発生する場合があります。",[103,8518,8520],{"className":771,"code":8519,"language":773,"meta":108,"style":108},"migrate -path db\u002Fmigration \\\n  -database \"postgresql:\u002F\u002Fusername:password@localhost:5432\u002Fdbname\" \\\n  -verbose up\n\n# エラー\nerror: pq: SSL is not enabled on the server\n",[90,8521,8522,8536,8546,8554,8558,8563],{"__ignoreMap":108},[112,8523,8524,8527,8530,8533],{"class":114,"line":115},[112,8525,8526],{"class":163},"migrate",[112,8528,8529],{"class":156}," -path",[112,8531,8532],{"class":136}," db\u002Fmigration",[112,8534,8535],{"class":156}," \\\n",[112,8537,8538,8541,8544],{"class":114,"line":122},[112,8539,8540],{"class":156},"  -database",[112,8542,8543],{"class":136}," \"postgresql:\u002F\u002Fusername:password@localhost:5432\u002Fdbname\"",[112,8545,8535],{"class":156},[112,8547,8548,8551],{"class":114,"line":143},[112,8549,8550],{"class":156},"  -verbose",[112,8552,8553],{"class":136}," up\n",[112,8555,8556],{"class":114,"line":150},[112,8557,147],{"emptyLinePlaceholder":146},[112,8559,8560],{"class":114,"line":170},[112,8561,8562],{"class":118},"# エラー\n",[112,8564,8565,8568,8571,8574,8577,8580,8583,8586,8589],{"class":114,"line":182},[112,8566,8567],{"class":163},"error:",[112,8569,8570],{"class":136}," pq:",[112,8572,8573],{"class":136}," SSL",[112,8575,8576],{"class":136}," is",[112,8578,8579],{"class":136}," not",[112,8581,8582],{"class":136}," enabled",[112,8584,8585],{"class":136}," on",[112,8587,8588],{"class":136}," the",[112,8590,8591],{"class":136}," server\n",[15,8593,8594],{},"このエラーは、PostgreSQLクライアントがデフォルトでSSL接続を試みるのに対し、サーバー側でSSLが有効になっていないために発生します。",[11,8596,8597],{"id":8597},"原因",[15,8599,8600,8601,8604],{},"PostgreSQLの公式Dockerイメージは、デフォルトで",[27,8602,8603],{},"SSLが無効","になっています。",[15,8606,8607,8608,8611,8612,8615],{},"一方、多くのPostgreSQLクライアントライブラリ（",[90,8609,8610],{},"pq","など）は、セキュリティのためデフォルトで",[27,8613,8614],{},"SSL接続を要求","します。",[15,8617,8618],{},"この設定の不一致がエラーの原因です。",[11,8620,8621],{"id":8621},"開発環境での対処方法",[83,8623,8625],{"id":8624},"sslモードを無効化する","SSLモードを無効化する",[15,8627,8628,8629,8632],{},"接続文字列に",[90,8630,8631],{},"?sslmode=disable","を追加します。",[103,8634,8636],{"className":771,"code":8635,"language":773,"meta":108,"style":108},"migrate -path db\u002Fmigration \\\n  -database \"postgresql:\u002F\u002Fusername:password@localhost:5432\u002Fdbname?sslmode=disable\" \\\n  -verbose up\n",[90,8637,8638,8648,8657],{"__ignoreMap":108},[112,8639,8640,8642,8644,8646],{"class":114,"line":115},[112,8641,8526],{"class":163},[112,8643,8529],{"class":156},[112,8645,8532],{"class":136},[112,8647,8535],{"class":156},[112,8649,8650,8652,8655],{"class":114,"line":122},[112,8651,8540],{"class":156},[112,8653,8654],{"class":136}," \"postgresql:\u002F\u002Fusername:password@localhost:5432\u002Fdbname?sslmode=disable\"",[112,8656,8535],{"class":156},[112,8658,8659,8661],{"class":114,"line":143},[112,8660,8550],{"class":156},[112,8662,8553],{"class":136},[83,8664,8666],{"id":8665},"goコードでの設定","Goコードでの設定",[103,8668,8672],{"className":8669,"code":8670,"language":8671,"meta":108,"style":108},"language-go shiki shiki-themes github-light github-dark","import (\n    \"database\u002Fsql\"\n    _ \"github.com\u002Flib\u002Fpq\"\n)\n\nfunc main() {\n    connStr := \"postgres:\u002F\u002Fusername:password@localhost:5432\u002Fdbname?sslmode=disable\"\n    db, err := sql.Open(\"postgres\", connStr)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer db.Close()\n}\n","go",[90,8673,8674,8679,8684,8689,8693,8697,8702,8707,8712,8717,8722,8726,8731],{"__ignoreMap":108},[112,8675,8676],{"class":114,"line":115},[112,8677,8678],{},"import (\n",[112,8680,8681],{"class":114,"line":122},[112,8682,8683],{},"    \"database\u002Fsql\"\n",[112,8685,8686],{"class":114,"line":143},[112,8687,8688],{},"    _ \"github.com\u002Flib\u002Fpq\"\n",[112,8690,8691],{"class":114,"line":150},[112,8692,431],{},[112,8694,8695],{"class":114,"line":170},[112,8696,147],{"emptyLinePlaceholder":146},[112,8698,8699],{"class":114,"line":182},[112,8700,8701],{},"func main() {\n",[112,8703,8704],{"class":114,"line":193},[112,8705,8706],{},"    connStr := \"postgres:\u002F\u002Fusername:password@localhost:5432\u002Fdbname?sslmode=disable\"\n",[112,8708,8709],{"class":114,"line":205},[112,8710,8711],{},"    db, err := sql.Open(\"postgres\", connStr)\n",[112,8713,8714],{"class":114,"line":241},[112,8715,8716],{},"    if err != nil {\n",[112,8718,8719],{"class":114,"line":247},[112,8720,8721],{},"        log.Fatal(err)\n",[112,8723,8724],{"class":114,"line":351},[112,8725,3118],{},[112,8727,8728],{"class":114,"line":361},[112,8729,8730],{},"    defer db.Close()\n",[112,8732,8733],{"class":114,"line":367},[112,8734,1452],{},[83,8736,8737],{"id":8737},"環境変数での管理",[103,8739,8741],{"className":771,"code":8740,"language":773,"meta":108,"style":108},"# .env\nDATABASE_URL=postgresql:\u002F\u002Fusername:password@localhost:5432\u002Fdbname?sslmode=disable\n",[90,8742,8743,8748],{"__ignoreMap":108},[112,8744,8745],{"class":114,"line":115},[112,8746,8747],{"class":118},"# .env\n",[112,8749,8750,8753,8755,8758,8761,8763],{"class":114,"line":122},[112,8751,8752],{"class":129},"DATABASE_URL",[112,8754,576],{"class":125},[112,8756,8757],{"class":136},"postgresql:\u002F\u002Fusername:password@localhost:5432\u002Fdbname?",[112,8759,8760],{"class":129},"sslmode",[112,8762,576],{"class":125},[112,8764,8765],{"class":136},"disable\n",[103,8767,8769],{"className":8669,"code":8768,"language":8671,"meta":108,"style":108},"import (\n    \"os\"\n    \"github.com\u002Fjoho\u002Fgodotenv\"\n)\n\nfunc main() {\n    godotenv.Load()\n    connStr := os.Getenv(\"DATABASE_URL\")\n    db, err := sql.Open(\"postgres\", connStr)\n    \u002F\u002F ...\n}\n",[90,8770,8771,8775,8780,8785,8789,8793,8797,8802,8807,8811,8816],{"__ignoreMap":108},[112,8772,8773],{"class":114,"line":115},[112,8774,8678],{},[112,8776,8777],{"class":114,"line":122},[112,8778,8779],{},"    \"os\"\n",[112,8781,8782],{"class":114,"line":143},[112,8783,8784],{},"    \"github.com\u002Fjoho\u002Fgodotenv\"\n",[112,8786,8787],{"class":114,"line":150},[112,8788,431],{},[112,8790,8791],{"class":114,"line":170},[112,8792,147],{"emptyLinePlaceholder":146},[112,8794,8795],{"class":114,"line":182},[112,8796,8701],{},[112,8798,8799],{"class":114,"line":193},[112,8800,8801],{},"    godotenv.Load()\n",[112,8803,8804],{"class":114,"line":205},[112,8805,8806],{},"    connStr := os.Getenv(\"DATABASE_URL\")\n",[112,8808,8809],{"class":114,"line":241},[112,8810,8711],{},[112,8812,8813],{"class":114,"line":247},[112,8814,8815],{},"    \u002F\u002F ...\n",[112,8817,8818],{"class":114,"line":351},[112,8819,1452],{},[11,8821,8823],{"id":8822},"sslモードの種類","SSLモードの種類",[15,8825,8826],{},"PostgreSQLは複数のSSLモードをサポートしています。",[7735,8828,8829,8842],{},[7738,8830,8831],{},[7741,8832,8833,8836,8839],{},[7744,8834,8835],{},"モード",[7744,8837,8838],{},"説明",[7744,8840,8841],{},"セキュリティレベル",[7754,8843,8844,8857,8870,8883,8896,8909],{},[7741,8845,8846,8851,8854],{},[7759,8847,8848],{},[90,8849,8850],{},"disable",[7759,8852,8853],{},"SSL接続を使用しない",[7759,8855,8856],{},"❌ 低",[7741,8858,8859,8864,8867],{},[7759,8860,8861],{},[90,8862,8863],{},"allow",[7759,8865,8866],{},"SSL接続を試み、失敗したら非SSL",[7759,8868,8869],{},"⚠️ 低〜中",[7741,8871,8872,8877,8880],{},[7759,8873,8874],{},[90,8875,8876],{},"prefer",[7759,8878,8879],{},"SSL接続を優先（デフォルト）",[7759,8881,8882],{},"⚠️ 中",[7741,8884,8885,8890,8893],{},[7759,8886,8887],{},[90,8888,8889],{},"require",[7759,8891,8892],{},"SSL接続を要求するが証明書は検証しない",[7759,8894,8895],{},"✅ 中〜高",[7741,8897,8898,8903,8906],{},[7759,8899,8900],{},[90,8901,8902],{},"verify-ca",[7759,8904,8905],{},"SSL接続とCA証明書を検証",[7759,8907,8908],{},"✅ 高",[7741,8910,8911,8916,8919],{},[7759,8912,8913],{},[90,8914,8915],{},"verify-full",[7759,8917,8918],{},"SSL接続とホスト名も検証",[7759,8920,8921],{},"✅✅ 最高",[83,8923,8924],{"id":8924},"各モードの使い分け",[103,8926,8928],{"className":771,"code":8927,"language":773,"meta":108,"style":108},"# 開発環境: SSL無効\npostgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fdb?sslmode=disable\n\n# ステージング: SSL必須だが証明書検証なし\npostgresql:\u002F\u002Fuser:pass@staging-db:5432\u002Fdb?sslmode=require\n\n# 本番環境: 完全な証明書検証\npostgresql:\u002F\u002Fuser:pass@prod-db:5432\u002Fdb?sslmode=verify-full\n",[90,8929,8930,8935,8943,8947,8952,8960,8964,8969],{"__ignoreMap":108},[112,8931,8932],{"class":114,"line":115},[112,8933,8934],{"class":118},"# 開発環境: SSL無効\n",[112,8936,8937,8940],{"class":114,"line":122},[112,8938,8939],{"class":163},"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fdb?sslmode",[112,8941,8942],{"class":136},"=disable\n",[112,8944,8945],{"class":114,"line":143},[112,8946,147],{"emptyLinePlaceholder":146},[112,8948,8949],{"class":114,"line":150},[112,8950,8951],{"class":118},"# ステージング: SSL必須だが証明書検証なし\n",[112,8953,8954,8957],{"class":114,"line":170},[112,8955,8956],{"class":163},"postgresql:\u002F\u002Fuser:pass@staging-db:5432\u002Fdb?sslmode",[112,8958,8959],{"class":136},"=require\n",[112,8961,8962],{"class":114,"line":182},[112,8963,147],{"emptyLinePlaceholder":146},[112,8965,8966],{"class":114,"line":193},[112,8967,8968],{"class":118},"# 本番環境: 完全な証明書検証\n",[112,8970,8971,8974],{"class":114,"line":205},[112,8972,8973],{"class":163},"postgresql:\u002F\u002Fuser:pass@prod-db:5432\u002Fdb?sslmode",[112,8975,8976],{"class":136},"=verify-full\n",[11,8978,8979],{"id":8979},"本番環境での推奨設定",[15,8981,8982,8983,8986],{},"本番環境では、セキュリティのため",[27,8984,8985],{},"必ずSSLを有効化","することを推奨します。",[83,8988,8990],{"id":8989},"postgresqlサーバーでsslを有効化","PostgreSQLサーバーでSSLを有効化",[8992,8993,8995],"h4",{"id":8994},"_1-ssl証明書の準備","1. SSL証明書の準備",[103,8997,8999],{"className":771,"code":8998,"language":773,"meta":108,"style":108},"# 自己署名証明書を生成（開発・テスト用）\nopenssl req -new -x509 -days 365 -nodes -text \\\n  -out server.crt \\\n  -keyout server.key \\\n  -subj \"\u002FCN=localhost\"\n\n# パーミッション設定\nchmod 600 server.key\nchown postgres:postgres server.key server.crt\n",[90,9000,9001,9006,9034,9044,9054,9062,9066,9071,9082],{"__ignoreMap":108},[112,9002,9003],{"class":114,"line":115},[112,9004,9005],{"class":118},"# 自己署名証明書を生成（開発・テスト用）\n",[112,9007,9008,9011,9014,9017,9020,9023,9026,9029,9032],{"class":114,"line":122},[112,9009,9010],{"class":163},"openssl",[112,9012,9013],{"class":136}," req",[112,9015,9016],{"class":156}," -new",[112,9018,9019],{"class":156}," -x509",[112,9021,9022],{"class":156}," -days",[112,9024,9025],{"class":156}," 365",[112,9027,9028],{"class":156}," -nodes",[112,9030,9031],{"class":156}," -text",[112,9033,8535],{"class":156},[112,9035,9036,9039,9042],{"class":114,"line":143},[112,9037,9038],{"class":156},"  -out",[112,9040,9041],{"class":136}," server.crt",[112,9043,8535],{"class":156},[112,9045,9046,9049,9052],{"class":114,"line":150},[112,9047,9048],{"class":156},"  -keyout",[112,9050,9051],{"class":136}," server.key",[112,9053,8535],{"class":156},[112,9055,9056,9059],{"class":114,"line":170},[112,9057,9058],{"class":156},"  -subj",[112,9060,9061],{"class":136}," \"\u002FCN=localhost\"\n",[112,9063,9064],{"class":114,"line":182},[112,9065,147],{"emptyLinePlaceholder":146},[112,9067,9068],{"class":114,"line":193},[112,9069,9070],{"class":118},"# パーミッション設定\n",[112,9072,9073,9076,9079],{"class":114,"line":205},[112,9074,9075],{"class":163},"chmod",[112,9077,9078],{"class":156}," 600",[112,9080,9081],{"class":136}," server.key\n",[112,9083,9084,9087,9090,9092],{"class":114,"line":241},[112,9085,9086],{"class":163},"chown",[112,9088,9089],{"class":136}," postgres:postgres",[112,9091,9051],{"class":136},[112,9093,9094],{"class":136}," server.crt\n",[8992,9096,9098],{"id":9097},"_2-postgresqlの設定","2. PostgreSQLの設定",[103,9100,9104],{"className":9101,"code":9102,"language":9103,"meta":108,"style":108},"language-ini shiki shiki-themes github-light github-dark","# postgresql.conf\nssl = on\nssl_cert_file = '\u002Fpath\u002Fto\u002Fserver.crt'\nssl_key_file = '\u002Fpath\u002Fto\u002Fserver.key'\n","ini",[90,9105,9106,9111,9116,9121],{"__ignoreMap":108},[112,9107,9108],{"class":114,"line":115},[112,9109,9110],{},"# postgresql.conf\n",[112,9112,9113],{"class":114,"line":122},[112,9114,9115],{},"ssl = on\n",[112,9117,9118],{"class":114,"line":143},[112,9119,9120],{},"ssl_cert_file = '\u002Fpath\u002Fto\u002Fserver.crt'\n",[112,9122,9123],{"class":114,"line":150},[112,9124,9125],{},"ssl_key_file = '\u002Fpath\u002Fto\u002Fserver.key'\n",[8992,9127,9129],{"id":9128},"_3-docker-composeでの設定例","3. Docker Composeでの設定例",[103,9131,9135],{"className":9132,"code":9133,"language":9134,"meta":108,"style":108},"language-yaml shiki shiki-themes github-light github-dark","version: '3.8'\n\nservices:\n  postgres:\n    image: postgres:15\n    environment:\n      POSTGRES_PASSWORD: password\n    volumes:\n      - .\u002Fcerts\u002Fserver.crt:\u002Fvar\u002Flib\u002Fpostgresql\u002Fserver.crt\n      - .\u002Fcerts\u002Fserver.key:\u002Fvar\u002Flib\u002Fpostgresql\u002Fserver.key\n    command: >\n      -c ssl=on\n      -c ssl_cert_file=\u002Fvar\u002Flib\u002Fpostgresql\u002Fserver.crt\n      -c ssl_key_file=\u002Fvar\u002Flib\u002Fpostgresql\u002Fserver.key\n","yaml",[90,9136,9137,9148,9152,9159,9166,9176,9183,9193,9200,9208,9215,9224,9229,9234],{"__ignoreMap":108},[112,9138,9139,9143,9145],{"class":114,"line":115},[112,9140,9142],{"class":9141},"s9eBZ","version",[112,9144,567],{"class":129},[112,9146,9147],{"class":136},"'3.8'\n",[112,9149,9150],{"class":114,"line":122},[112,9151,147],{"emptyLinePlaceholder":146},[112,9153,9154,9157],{"class":114,"line":143},[112,9155,9156],{"class":9141},"services",[112,9158,5004],{"class":129},[112,9160,9161,9164],{"class":114,"line":150},[112,9162,9163],{"class":9141},"  postgres",[112,9165,5004],{"class":129},[112,9167,9168,9171,9173],{"class":114,"line":170},[112,9169,9170],{"class":9141},"    image",[112,9172,567],{"class":129},[112,9174,9175],{"class":136},"postgres:15\n",[112,9177,9178,9181],{"class":114,"line":182},[112,9179,9180],{"class":9141},"    environment",[112,9182,5004],{"class":129},[112,9184,9185,9188,9190],{"class":114,"line":193},[112,9186,9187],{"class":9141},"      POSTGRES_PASSWORD",[112,9189,567],{"class":129},[112,9191,9192],{"class":136},"password\n",[112,9194,9195,9198],{"class":114,"line":205},[112,9196,9197],{"class":9141},"    volumes",[112,9199,5004],{"class":129},[112,9201,9202,9205],{"class":114,"line":241},[112,9203,9204],{"class":129},"      - ",[112,9206,9207],{"class":136},".\u002Fcerts\u002Fserver.crt:\u002Fvar\u002Flib\u002Fpostgresql\u002Fserver.crt\n",[112,9209,9210,9212],{"class":114,"line":247},[112,9211,9204],{"class":129},[112,9213,9214],{"class":136},".\u002Fcerts\u002Fserver.key:\u002Fvar\u002Flib\u002Fpostgresql\u002Fserver.key\n",[112,9216,9217,9220,9222],{"class":114,"line":351},[112,9218,9219],{"class":9141},"    command",[112,9221,567],{"class":129},[112,9223,6967],{"class":125},[112,9225,9226],{"class":114,"line":361},[112,9227,9228],{"class":136},"      -c ssl=on\n",[112,9230,9231],{"class":114,"line":367},[112,9232,9233],{"class":136},"      -c ssl_cert_file=\u002Fvar\u002Flib\u002Fpostgresql\u002Fserver.crt\n",[112,9235,9236],{"class":114,"line":373},[112,9237,9238],{"class":136},"      -c ssl_key_file=\u002Fvar\u002Flib\u002Fpostgresql\u002Fserver.key\n",[83,9240,9241],{"id":9241},"クライアント側の接続設定",[103,9243,9245],{"className":771,"code":9244,"language":773,"meta":108,"style":108},"# SSL必須で接続\nmigrate -path db\u002Fmigration \\\n  -database \"postgresql:\u002F\u002Fuser:pass@db:5432\u002Fmydb?sslmode=require\" \\\n  -verbose up\n",[90,9246,9247,9252,9262,9271],{"__ignoreMap":108},[112,9248,9249],{"class":114,"line":115},[112,9250,9251],{"class":118},"# SSL必須で接続\n",[112,9253,9254,9256,9258,9260],{"class":114,"line":122},[112,9255,8526],{"class":163},[112,9257,8529],{"class":156},[112,9259,8532],{"class":136},[112,9261,8535],{"class":156},[112,9263,9264,9266,9269],{"class":114,"line":143},[112,9265,8540],{"class":156},[112,9267,9268],{"class":136}," \"postgresql:\u002F\u002Fuser:pass@db:5432\u002Fmydb?sslmode=require\"",[112,9270,8535],{"class":156},[112,9272,9273,9275],{"class":114,"line":150},[112,9274,8550],{"class":156},[112,9276,8553],{"class":136},[11,9278,9279],{"id":9279},"セキュリティ上の考慮事項",[83,9281,9282],{"id":9282},"ローカル開発環境",[31,9284,9285,9291],{},[34,9286,9287,9290],{},[90,9288,9289],{},"sslmode=disable","でも問題ない",[34,9292,9293],{},"ネットワークが信頼できる環境（localhost）",[83,9295,9297],{"id":9296},"ステージング本番環境","ステージング\u002F本番環境",[31,9299,9300,9307,9314],{},[34,9301,9302,9303,9306],{},"最低でも",[90,9304,9305],{},"sslmode=require","を使用",[34,9308,9309,9310,9313],{},"可能であれば",[90,9311,9312],{},"sslmode=verify-full","を推奨",[34,9315,9316],{},"Let's Encryptなどで正式な証明書を取得",[83,9318,9319],{"id":9319},"接続文字列の管理",[103,9321,9323],{"className":8669,"code":9322,"language":8671,"meta":108,"style":108},"\u002F\u002F ❌ ハードコード（危険）\nconnStr := \"postgres:\u002F\u002Fadmin:password123@prod-db\u002Fmydb\"\n\n\u002F\u002F ✅ 環境変数で管理\nconnStr := os.Getenv(\"DATABASE_URL\")\n\n\u002F\u002F ✅ シークレット管理ツールを使用\n\u002F\u002F AWS Secrets Manager, Google Secret Manager, HashiCorp Vault など\n",[90,9324,9325,9330,9335,9339,9344,9349,9353,9358],{"__ignoreMap":108},[112,9326,9327],{"class":114,"line":115},[112,9328,9329],{},"\u002F\u002F ❌ ハードコード（危険）\n",[112,9331,9332],{"class":114,"line":122},[112,9333,9334],{},"connStr := \"postgres:\u002F\u002Fadmin:password123@prod-db\u002Fmydb\"\n",[112,9336,9337],{"class":114,"line":143},[112,9338,147],{"emptyLinePlaceholder":146},[112,9340,9341],{"class":114,"line":150},[112,9342,9343],{},"\u002F\u002F ✅ 環境変数で管理\n",[112,9345,9346],{"class":114,"line":170},[112,9347,9348],{},"connStr := os.Getenv(\"DATABASE_URL\")\n",[112,9350,9351],{"class":114,"line":182},[112,9352,147],{"emptyLinePlaceholder":146},[112,9354,9355],{"class":114,"line":193},[112,9356,9357],{},"\u002F\u002F ✅ シークレット管理ツールを使用\n",[112,9359,9360],{"class":114,"line":205},[112,9361,9362],{},"\u002F\u002F AWS Secrets Manager, Google Secret Manager, HashiCorp Vault など\n",[11,9364,9365],{"id":9365},"トラブルシューティング",[83,9367,9369],{"id":9368},"エラー-certificate-verify-failed","エラー: certificate verify failed",[103,9371,9373],{"className":771,"code":9372,"language":773,"meta":108,"style":108},"error: x509: certificate signed by unknown authority\n",[90,9374,9375],{"__ignoreMap":108},[112,9376,9377,9379,9382,9385,9388,9391,9394],{"class":114,"line":115},[112,9378,8567],{"class":163},[112,9380,9381],{"class":136}," x509:",[112,9383,9384],{"class":136}," certificate",[112,9386,9387],{"class":136}," signed",[112,9389,9390],{"class":136}," by",[112,9392,9393],{"class":136}," unknown",[112,9395,9396],{"class":136}," authority\n",[15,9398,9399,9402],{},[27,9400,9401],{},"対処法",": CA証明書を指定する",[103,9404,9406],{"className":771,"code":9405,"language":773,"meta":108,"style":108},"postgresql:\u002F\u002Fuser:pass@db:5432\u002Fmydb?sslmode=verify-ca&sslrootcert=\u002Fpath\u002Fto\u002Fca.crt\n",[90,9407,9408],{"__ignoreMap":108},[112,9409,9410,9413,9416,9419,9421],{"class":114,"line":115},[112,9411,9412],{"class":163},"postgresql:\u002F\u002Fuser:pass@db:5432\u002Fmydb?sslmode",[112,9414,9415],{"class":136},"=verify-ca",[112,9417,9418],{"class":129},"&sslrootcert",[112,9420,576],{"class":125},[112,9422,9423],{"class":136},"\u002Fpath\u002Fto\u002Fca.crt\n",[83,9425,9427],{"id":9426},"エラー-server-does-not-support-ssl","エラー: server does not support SSL",[15,9429,9430],{},"PostgreSQLサーバーでSSLが無効になっています。",[103,9432,9434],{"className":9101,"code":9433,"language":9103,"meta":108,"style":108},"# postgresql.conf\nssl = on  # この設定を確認\n",[90,9435,9436,9440],{"__ignoreMap":108},[112,9437,9438],{"class":114,"line":115},[112,9439,9110],{},[112,9441,9442],{"class":114,"line":122},[112,9443,9444],{},"ssl = on  # この設定を確認\n",[83,9446,9448],{"id":9447},"docker環境での証明書パス問題","Docker環境での証明書パス問題",[15,9450,9451],{},"コンテナ内のパスを正しく指定します。",[103,9453,9455],{"className":9132,"code":9454,"language":9134,"meta":108,"style":108},"volumes:\n  - .\u002Fcerts:\u002Fcerts:ro\n\ncommand: >\n  -c ssl=on\n  -c ssl_cert_file=\u002Fcerts\u002Fserver.crt\n  -c ssl_key_file=\u002Fcerts\u002Fserver.key\n",[90,9456,9457,9464,9472,9476,9485,9490,9495],{"__ignoreMap":108},[112,9458,9459,9462],{"class":114,"line":115},[112,9460,9461],{"class":9141},"volumes",[112,9463,5004],{"class":129},[112,9465,9466,9469],{"class":114,"line":122},[112,9467,9468],{"class":129},"  - ",[112,9470,9471],{"class":136},".\u002Fcerts:\u002Fcerts:ro\n",[112,9473,9474],{"class":114,"line":143},[112,9475,147],{"emptyLinePlaceholder":146},[112,9477,9478,9481,9483],{"class":114,"line":150},[112,9479,9480],{"class":9141},"command",[112,9482,567],{"class":129},[112,9484,6967],{"class":125},[112,9486,9487],{"class":114,"line":170},[112,9488,9489],{"class":136},"  -c ssl=on\n",[112,9491,9492],{"class":114,"line":182},[112,9493,9494],{"class":136},"  -c ssl_cert_file=\u002Fcerts\u002Fserver.crt\n",[112,9496,9497],{"class":114,"line":193},[112,9498,9499],{"class":136},"  -c ssl_key_file=\u002Fcerts\u002Fserver.key\n",[11,9501,919],{"id":919},[15,9503,9504],{},"PostgreSQL接続でのSSLエラーを解決する方法は次の通りです。",[31,9506,9507,9516,9525,9531],{},[34,9508,9509,9512,9513,9515],{},[27,9510,9511],{},"開発環境"," ",[90,9514,9289],{},"で問題なし",[34,9517,9518,9521,9522,9524],{},[27,9519,9520],{},"本番環境"," 必ずSSLを有効化し、",[90,9523,9305],{},"以上を使用",[34,9526,9527,9530],{},[27,9528,9529],{},"セキュリティ"," 接続文字列は環境変数やシークレット管理ツールで管理",[34,9532,9533,9536],{},[27,9534,9535],{},"証明書"," 本番環境では正式な証明書を使用",[15,9538,9539],{},"環境に応じてSSLモードを選択することが重要です。",[83,9541,2930],{"id":2930},[31,9543,9544,9551,9558],{},[34,9545,9546],{},[759,9547,9550],{"href":9548,"rel":9549},"https:\u002F\u002Fwww.postgresql.jp\u002Fdocument\u002Fcurrent\u002Fhtml\u002Flibpq-ssl.html",[763],"PostgreSQL SSL サポート",[34,9552,9553],{},[759,9554,9557],{"href":9555,"rel":9556},"https:\u002F\u002Fgithub.com\u002Fgolang-migrate\u002Fmigrate",[763],"golang-migrate",[34,9559,9560],{},[759,9561,9564],{"href":9562,"rel":9563},"https:\u002F\u002Fgithub.com\u002Flib\u002Fpq",[763],"pq ドライバー",[944,9566,9567],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":108,"searchDepth":122,"depth":122,"links":9569},[9570,9571,9572,9577,9580,9584,9589,9594],{"id":8513,"depth":122,"text":8513},{"id":8597,"depth":122,"text":8597},{"id":8621,"depth":122,"text":8621,"children":9573},[9574,9575,9576],{"id":8624,"depth":143,"text":8625},{"id":8665,"depth":143,"text":8666},{"id":8737,"depth":143,"text":8737},{"id":8822,"depth":122,"text":8823,"children":9578},[9579],{"id":8924,"depth":143,"text":8924},{"id":8979,"depth":122,"text":8979,"children":9581},[9582,9583],{"id":8989,"depth":143,"text":8990},{"id":9241,"depth":143,"text":9241},{"id":9279,"depth":122,"text":9279,"children":9585},[9586,9587,9588],{"id":9282,"depth":143,"text":9282},{"id":9296,"depth":143,"text":9297},{"id":9319,"depth":143,"text":9319},{"id":9365,"depth":122,"text":9365,"children":9590},[9591,9592,9593],{"id":9368,"depth":143,"text":9369},{"id":9426,"depth":143,"text":9427},{"id":9447,"depth":143,"text":9448},{"id":919,"depth":122,"text":919,"children":9595},[9596],{"id":2930,"depth":143,"text":2930},"2022-10-30","golang-migrateでPostgreSQLに接続する際のSSLエラーの原因と、開発環境・本番環境それぞれでの適切な対処方法を解説します。",{"tags":9600},[9601,9602,9603],"postgresql","golang","ssl","\u002Fblog\u002Fpostgresql-ssl-connection-error",{"title":8508,"description":9598},"blog\u002Fpostgresql-ssl-connection-error","9eIYVTSW6bj_N3n-p0JJYwCPeyF390FGXp3eQQGGl_A",{"id":9609,"title":9610,"body":9611,"date":12794,"description":12795,"extension":962,"meta":12796,"navigation":146,"path":12801,"seo":12802,"stem":12803,"__hash__":12804},"blog\u002Fblog\u002Fnestjs-auth0-user-registration.md","NestJSでAuth0 Management APIを使ったユーザー登録",{"type":8,"value":9612,"toc":12763},[9613,9617,9620,9625,9639,9642,9644,9670,9674,9681,9703,9707,9710,9736,9740,9743,9808,9844,9848,9852,9938,9942,10871,10875,10879,11346,11350,11466,11483,11487,11491,11661,11665,11907,11911,12002,12005,12009,12012,12023,12027,12143,12147,12233,12237,12240,12353,12356,12696,12698,12701,12732,12735,12737,12760],[11,9614,9616],{"id":9615},"auth0-management-apiとは","Auth0 Management APIとは",[15,9618,9619],{},"Auth0 Management APIは、Auth0のリソース（ユーザー、アプリケーション、接続など）をプログラムから操作するためのREST APIです。",[15,9621,9622],{},[27,9623,9624],{},"主な用途",[31,9626,9627,9630,9633,9636],{},[34,9628,9629],{},"ユーザーの作成・更新・削除",[34,9631,9632],{},"ロールやパーミッションの管理",[34,9634,9635],{},"ログの取得と分析",[34,9637,9638],{},"アプリケーション設定の管理",[11,9640,9641],{"id":9641},"事前準備",[83,9643,7844],{"id":7843},[103,9645,9647],{"className":771,"code":9646,"language":773,"meta":108,"style":108},"npm install auth0\nnpm install -D @types\u002Fauth0\n",[90,9648,9649,9658],{"__ignoreMap":108},[112,9650,9651,9653,9655],{"class":114,"line":115},[112,9652,6832],{"class":163},[112,9654,7883],{"class":136},[112,9656,9657],{"class":136}," auth0\n",[112,9659,9660,9662,9664,9667],{"class":114,"line":122},[112,9661,6832],{"class":163},[112,9663,7883],{"class":136},[112,9665,9666],{"class":156}," -D",[112,9668,9669],{"class":136}," @types\u002Fauth0\n",[83,9671,9673],{"id":9672},"_2-auth0でアプリケーションを作成","2. Auth0でアプリケーションを作成",[15,9675,9676,9677,9680],{},"Auth0ダッシュボードで",[27,9678,9679],{},"Machine to Machine Application","を作成し、以下の情報を取得します",[31,9682,9683,9691,9697],{},[34,9684,9685,567,9688],{},[27,9686,9687],{},"Domain",[90,9689,9690],{},"your-tenant.auth0.com",[34,9692,9693,9696],{},[27,9694,9695],{},"Client ID",": アプリケーションID",[34,9698,9699,9702],{},[27,9700,9701],{},"Client Secret",": シークレットキー",[83,9704,9706],{"id":9705},"_3-必要なスコープを付与","3. 必要なスコープを付与",[15,9708,9709],{},"Management APIに対して以下のスコープを付与します",[31,9711,9712,9718,9724,9730],{},[34,9713,9714,9717],{},[90,9715,9716],{},"create:users",": ユーザーの作成",[34,9719,9720,9723],{},[90,9721,9722],{},"read:users",": ユーザー情報の取得",[34,9725,9726,9729],{},[90,9727,9728],{},"update:users",": ユーザー情報の更新",[34,9731,9732,9735],{},[90,9733,9734],{},"delete:users",": ユーザーの削除（必要に応じて）",[11,9737,9739],{"id":9738},"nestjsでの実装","NestJSでの実装",[83,9741,9742],{"id":9742},"環境変数の設定",[103,9744,9746],{"className":105,"code":9745,"language":107,"meta":108,"style":108},"\u002F\u002F config\u002Fconfiguration.ts\nexport default () => ({\n  auth0: {\n    domain: process.env.AUTH0_DOMAIN,\n    clientId: process.env.AUTH0_CLIENT_ID,\n    clientSecret: process.env.AUTH0_CLIENT_SECRET,\n  },\n})\n",[90,9747,9748,9753,9765,9770,9780,9790,9800,9804],{"__ignoreMap":108},[112,9749,9750],{"class":114,"line":115},[112,9751,9752],{"class":118},"\u002F\u002F config\u002Fconfiguration.ts\n",[112,9754,9755,9757,9759,9761,9763],{"class":114,"line":122},[112,9756,288],{"class":125},[112,9758,291],{"class":125},[112,9760,3802],{"class":129},[112,9762,229],{"class":125},[112,9764,2097],{"class":129},[112,9766,9767],{"class":114,"line":143},[112,9768,9769],{"class":129},"  auth0: {\n",[112,9771,9772,9775,9778],{"class":114,"line":150},[112,9773,9774],{"class":129},"    domain: process.env.",[112,9776,9777],{"class":156},"AUTH0_DOMAIN",[112,9779,179],{"class":129},[112,9781,9782,9785,9788],{"class":114,"line":170},[112,9783,9784],{"class":129},"    clientId: process.env.",[112,9786,9787],{"class":156},"AUTH0_CLIENT_ID",[112,9789,179],{"class":129},[112,9791,9792,9795,9798],{"class":114,"line":182},[112,9793,9794],{"class":129},"    clientSecret: process.env.",[112,9796,9797],{"class":156},"AUTH0_CLIENT_SECRET",[112,9799,179],{"class":129},[112,9801,9802],{"class":114,"line":193},[112,9803,376],{"class":129},[112,9805,9806],{"class":114,"line":205},[112,9807,8436],{"class":129},[103,9809,9811],{"className":771,"code":9810,"language":773,"meta":108,"style":108},"# .env\nAUTH0_DOMAIN=your-tenant.auth0.com\nAUTH0_CLIENT_ID=your_client_id\nAUTH0_CLIENT_SECRET=your_client_secret\n",[90,9812,9813,9817,9826,9835],{"__ignoreMap":108},[112,9814,9815],{"class":114,"line":115},[112,9816,8747],{"class":118},[112,9818,9819,9821,9823],{"class":114,"line":122},[112,9820,9777],{"class":129},[112,9822,576],{"class":125},[112,9824,9825],{"class":136},"your-tenant.auth0.com\n",[112,9827,9828,9830,9832],{"class":114,"line":143},[112,9829,9787],{"class":129},[112,9831,576],{"class":125},[112,9833,9834],{"class":136},"your_client_id\n",[112,9836,9837,9839,9841],{"class":114,"line":150},[112,9838,9797],{"class":129},[112,9840,576],{"class":125},[112,9842,9843],{"class":136},"your_client_secret\n",[83,9845,9847],{"id":9846},"auth0モジュールの実装","Auth0モジュールの実装",[8992,9849,9851],{"id":9850},"module","Module",[103,9853,9855],{"className":105,"code":9854,"language":107,"meta":108,"style":108},"import { Module } from '@nestjs\u002Fcommon'\nimport { ConfigModule } from '@nestjs\u002Fconfig'\nimport { Auth0Service } from '.\u002Fauth0.service'\n\n@Module({\n  imports: [ConfigModule],\n  providers: [Auth0Service],\n  exports: [Auth0Service],\n})\nexport class Auth0Module {}\n",[90,9856,9857,9869,9881,9893,9897,9906,9911,9916,9921,9925],{"__ignoreMap":108},[112,9858,9859,9861,9864,9866],{"class":114,"line":115},[112,9860,126],{"class":125},[112,9862,9863],{"class":129}," { Module } ",[112,9865,133],{"class":125},[112,9867,9868],{"class":136}," '@nestjs\u002Fcommon'\n",[112,9870,9871,9873,9876,9878],{"class":114,"line":122},[112,9872,126],{"class":125},[112,9874,9875],{"class":129}," { ConfigModule } ",[112,9877,133],{"class":125},[112,9879,9880],{"class":136}," '@nestjs\u002Fconfig'\n",[112,9882,9883,9885,9888,9890],{"class":114,"line":143},[112,9884,126],{"class":125},[112,9886,9887],{"class":129}," { Auth0Service } ",[112,9889,133],{"class":125},[112,9891,9892],{"class":136}," '.\u002Fauth0.service'\n",[112,9894,9895],{"class":114,"line":150},[112,9896,147],{"emptyLinePlaceholder":146},[112,9898,9899,9902,9904],{"class":114,"line":170},[112,9900,9901],{"class":129},"@",[112,9903,9851],{"class":163},[112,9905,167],{"class":129},[112,9907,9908],{"class":114,"line":182},[112,9909,9910],{"class":129},"  imports: [ConfigModule],\n",[112,9912,9913],{"class":114,"line":193},[112,9914,9915],{"class":129},"  providers: [Auth0Service],\n",[112,9917,9918],{"class":114,"line":205},[112,9919,9920],{"class":129},"  exports: [Auth0Service],\n",[112,9922,9923],{"class":114,"line":241},[112,9924,8436],{"class":129},[112,9926,9927,9929,9932,9935],{"class":114,"line":247},[112,9928,288],{"class":125},[112,9930,9931],{"class":125}," class",[112,9933,9934],{"class":163}," Auth0Module",[112,9936,9937],{"class":129}," {}\n",[8992,9939,9941],{"id":9940},"service","Service",[103,9943,9945],{"className":105,"code":9944,"language":107,"meta":108,"style":108},"import { Injectable, InternalServerErrorException } from '@nestjs\u002Fcommon'\nimport { ConfigService } from '@nestjs\u002Fconfig'\nimport { ManagementClient, User } from 'auth0'\n\nexport interface CreateUserInput {\n  email: string\n  password: string\n  name?: string\n}\n\nexport interface CreateUserOutput {\n  userId: string\n  email: string\n}\n\n@Injectable()\nexport class Auth0Service {\n  private readonly client: ManagementClient\n  private readonly connection = 'Username-Password-Authentication'\n\n  constructor(private configService: ConfigService) {\n    this.client = new ManagementClient({\n      domain: this.configService.get\u003Cstring>('auth0.domain')!,\n      clientId: this.configService.get\u003Cstring>('auth0.clientId')!,\n      clientSecret: this.configService.get\u003Cstring>('auth0.clientSecret')!,\n      scope: 'create:users read:users update:users',\n    })\n  }\n\n  async createUser(input: CreateUserInput): Promise\u003CCreateUserOutput> {\n    try {\n      const user = await this.client.createUser({\n        connection: this.connection,\n        email: input.email,\n        password: input.password,\n        name: input.name,\n        email_verified: false,\n      })\n\n      if (!user.user_id) {\n        throw new Error('User ID not returned from Auth0')\n      }\n\n      return {\n        userId: user.user_id,\n        email: user.email!,\n      }\n    } catch (error) {\n      this.handleAuth0Error(error)\n    }\n  }\n\n  async getUserById(userId: string): Promise\u003CUser> {\n    try {\n      return await this.client.getUser({ id: userId })\n    } catch (error) {\n      this.handleAuth0Error(error)\n    }\n  }\n\n  async updateUser(userId: string, data: Partial\u003CUser>): Promise\u003CUser> {\n    try {\n      return await this.client.updateUser({ id: userId }, data)\n    } catch (error) {\n      this.handleAuth0Error(error)\n    }\n  }\n\n  async deleteUser(userId: string): Promise\u003Cvoid> {\n    try {\n      await this.client.deleteUser({ id: userId })\n    } catch (error) {\n      this.handleAuth0Error(error)\n    }\n  }\n\n  private handleAuth0Error(error: any): never {\n    if (error.statusCode === 409) {\n      throw new InternalServerErrorException('User already exists')\n    }\n\n    if (error.statusCode === 400) {\n      throw new InternalServerErrorException(\n        `Invalid input: ${error.message}`,\n      )\n    }\n\n    throw new InternalServerErrorException(\n      `Auth0 error: ${error.message || 'Unknown error'}`,\n    )\n  }\n}\n",[90,9946,9947,9958,9969,9981,9985,9997,10007,10016,10026,10030,10034,10045,10054,10062,10066,10070,10079,10090,10106,10120,10124,10144,10161,10191,10217,10243,10253,10258,10262,10266,10298,10305,10327,10337,10342,10347,10352,10362,10366,10370,10381,10397,10401,10405,10411,10416,10425,10429,10438,10450,10454,10458,10462,10492,10498,10514,10522,10532,10536,10540,10544,10585,10591,10607,10615,10625,10629,10633,10637,10665,10671,10684,10692,10702,10706,10710,10714,10739,10753,10769,10773,10777,10790,10800,10816,10821,10825,10829,10839,10858,10863,10867],{"__ignoreMap":108},[112,9948,9949,9951,9954,9956],{"class":114,"line":115},[112,9950,126],{"class":125},[112,9952,9953],{"class":129}," { Injectable, InternalServerErrorException } ",[112,9955,133],{"class":125},[112,9957,9868],{"class":136},[112,9959,9960,9962,9965,9967],{"class":114,"line":122},[112,9961,126],{"class":125},[112,9963,9964],{"class":129}," { ConfigService } ",[112,9966,133],{"class":125},[112,9968,9880],{"class":136},[112,9970,9971,9973,9976,9978],{"class":114,"line":143},[112,9972,126],{"class":125},[112,9974,9975],{"class":129}," { ManagementClient, User } ",[112,9977,133],{"class":125},[112,9979,9980],{"class":136}," 'auth0'\n",[112,9982,9983],{"class":114,"line":150},[112,9984,147],{"emptyLinePlaceholder":146},[112,9986,9987,9989,9992,9995],{"class":114,"line":170},[112,9988,288],{"class":125},[112,9990,9991],{"class":125}," interface",[112,9993,9994],{"class":163}," CreateUserInput",[112,9996,1294],{"class":129},[112,9998,9999,10002,10004],{"class":114,"line":182},[112,10000,10001],{"class":222},"  email",[112,10003,2243],{"class":125},[112,10005,10006],{"class":156}," string\n",[112,10008,10009,10012,10014],{"class":114,"line":193},[112,10010,10011],{"class":222},"  password",[112,10013,2243],{"class":125},[112,10015,10006],{"class":156},[112,10017,10018,10021,10024],{"class":114,"line":205},[112,10019,10020],{"class":222},"  name",[112,10022,10023],{"class":125},"?:",[112,10025,10006],{"class":156},[112,10027,10028],{"class":114,"line":241},[112,10029,1452],{"class":129},[112,10031,10032],{"class":114,"line":247},[112,10033,147],{"emptyLinePlaceholder":146},[112,10035,10036,10038,10040,10043],{"class":114,"line":351},[112,10037,288],{"class":125},[112,10039,9991],{"class":125},[112,10041,10042],{"class":163}," CreateUserOutput",[112,10044,1294],{"class":129},[112,10046,10047,10050,10052],{"class":114,"line":361},[112,10048,10049],{"class":222},"  userId",[112,10051,2243],{"class":125},[112,10053,10006],{"class":156},[112,10055,10056,10058,10060],{"class":114,"line":367},[112,10057,10001],{"class":222},[112,10059,2243],{"class":125},[112,10061,10006],{"class":156},[112,10063,10064],{"class":114,"line":373},[112,10065,1452],{"class":129},[112,10067,10068],{"class":114,"line":379},[112,10069,147],{"emptyLinePlaceholder":146},[112,10071,10072,10074,10077],{"class":114,"line":1302},[112,10073,9901],{"class":129},[112,10075,10076],{"class":163},"Injectable",[112,10078,630],{"class":129},[112,10080,10081,10083,10085,10088],{"class":114,"line":1502},[112,10082,288],{"class":125},[112,10084,9931],{"class":125},[112,10086,10087],{"class":163}," Auth0Service",[112,10089,1294],{"class":129},[112,10091,10092,10095,10098,10101,10103],{"class":114,"line":1507},[112,10093,10094],{"class":125},"  private",[112,10096,10097],{"class":125}," readonly",[112,10099,10100],{"class":222}," client",[112,10102,2243],{"class":125},[112,10104,10105],{"class":163}," ManagementClient\n",[112,10107,10108,10110,10112,10115,10117],{"class":114,"line":1512},[112,10109,10094],{"class":125},[112,10111,10097],{"class":125},[112,10113,10114],{"class":222}," connection",[112,10116,160],{"class":125},[112,10118,10119],{"class":136}," 'Username-Password-Authentication'\n",[112,10121,10122],{"class":114,"line":1518},[112,10123,147],{"emptyLinePlaceholder":146},[112,10125,10126,10129,10131,10134,10137,10139,10142],{"class":114,"line":1524},[112,10127,10128],{"class":125},"  constructor",[112,10130,425],{"class":129},[112,10132,10133],{"class":125},"private",[112,10135,10136],{"class":222}," configService",[112,10138,2243],{"class":125},[112,10140,10141],{"class":163}," ConfigService",[112,10143,1969],{"class":129},[112,10145,10146,10149,10152,10154,10156,10159],{"class":114,"line":1530},[112,10147,10148],{"class":156},"    this",[112,10150,10151],{"class":129},".client ",[112,10153,576],{"class":125},[112,10155,232],{"class":125},[112,10157,10158],{"class":163}," ManagementClient",[112,10160,167],{"class":129},[112,10162,10163,10166,10169,10172,10174,10176,10178,10181,10184,10187,10189],{"class":114,"line":1536},[112,10164,10165],{"class":129},"      domain: ",[112,10167,10168],{"class":156},"this",[112,10170,10171],{"class":129},".configService.",[112,10173,3344],{"class":163},[112,10175,6944],{"class":129},[112,10177,199],{"class":156},[112,10179,10180],{"class":129},">(",[112,10182,10183],{"class":136},"'auth0.domain'",[112,10185,10186],{"class":129},")",[112,10188,3948],{"class":125},[112,10190,179],{"class":129},[112,10192,10193,10196,10198,10200,10202,10204,10206,10208,10211,10213,10215],{"class":114,"line":1542},[112,10194,10195],{"class":129},"      clientId: ",[112,10197,10168],{"class":156},[112,10199,10171],{"class":129},[112,10201,3344],{"class":163},[112,10203,6944],{"class":129},[112,10205,199],{"class":156},[112,10207,10180],{"class":129},[112,10209,10210],{"class":136},"'auth0.clientId'",[112,10212,10186],{"class":129},[112,10214,3948],{"class":125},[112,10216,179],{"class":129},[112,10218,10219,10222,10224,10226,10228,10230,10232,10234,10237,10239,10241],{"class":114,"line":2402},[112,10220,10221],{"class":129},"      clientSecret: ",[112,10223,10168],{"class":156},[112,10225,10171],{"class":129},[112,10227,3344],{"class":163},[112,10229,6944],{"class":129},[112,10231,199],{"class":156},[112,10233,10180],{"class":129},[112,10235,10236],{"class":136},"'auth0.clientSecret'",[112,10238,10186],{"class":129},[112,10240,3948],{"class":125},[112,10242,179],{"class":129},[112,10244,10245,10248,10251],{"class":114,"line":2407},[112,10246,10247],{"class":129},"      scope: ",[112,10249,10250],{"class":136},"'create:users read:users update:users'",[112,10252,179],{"class":129},[112,10254,10255],{"class":114,"line":2413},[112,10256,10257],{"class":129},"    })\n",[112,10259,10260],{"class":114,"line":2446},[112,10261,3232],{"class":129},[112,10263,10264],{"class":114,"line":2451},[112,10265,147],{"emptyLinePlaceholder":146},[112,10267,10268,10271,10274,10276,10279,10281,10283,10285,10287,10290,10292,10295],{"class":114,"line":2464},[112,10269,10270],{"class":125},"  async",[112,10272,10273],{"class":163}," createUser",[112,10275,425],{"class":129},[112,10277,10278],{"class":222},"input",[112,10280,2243],{"class":125},[112,10282,9994],{"class":163},[112,10284,10186],{"class":129},[112,10286,2243],{"class":125},[112,10288,10289],{"class":163}," Promise",[112,10291,6944],{"class":129},[112,10293,10294],{"class":163},"CreateUserOutput",[112,10296,10297],{"class":129},"> {\n",[112,10299,10300,10303],{"class":114,"line":2481},[112,10301,10302],{"class":125},"    try",[112,10304,1294],{"class":129},[112,10306,10307,10309,10312,10314,10316,10319,10322,10325],{"class":114,"line":2486},[112,10308,3575],{"class":125},[112,10310,10311],{"class":156}," user",[112,10313,160],{"class":125},[112,10315,419],{"class":125},[112,10317,10318],{"class":156}," this",[112,10320,10321],{"class":129},".client.",[112,10323,10324],{"class":163},"createUser",[112,10326,167],{"class":129},[112,10328,10329,10332,10334],{"class":114,"line":3229},[112,10330,10331],{"class":129},"        connection: ",[112,10333,10168],{"class":156},[112,10335,10336],{"class":129},".connection,\n",[112,10338,10339],{"class":114,"line":3235},[112,10340,10341],{"class":129},"        email: input.email,\n",[112,10343,10344],{"class":114,"line":3657},[112,10345,10346],{"class":129},"        password: input.password,\n",[112,10348,10349],{"class":114,"line":3662},[112,10350,10351],{"class":129},"        name: input.name,\n",[112,10353,10354,10357,10360],{"class":114,"line":3667},[112,10355,10356],{"class":129},"        email_verified: ",[112,10358,10359],{"class":156},"false",[112,10361,179],{"class":129},[112,10363,10364],{"class":114,"line":3680},[112,10365,8186],{"class":129},[112,10367,10368],{"class":114,"line":3692},[112,10369,147],{"emptyLinePlaceholder":146},[112,10371,10372,10374,10376,10378],{"class":114,"line":3716},[112,10373,3618],{"class":125},[112,10375,3945],{"class":129},[112,10377,3948],{"class":125},[112,10379,10380],{"class":129},"user.user_id) {\n",[112,10382,10383,10386,10388,10390,10392,10395],{"class":114,"line":3737},[112,10384,10385],{"class":125},"        throw",[112,10387,232],{"class":125},[112,10389,3524],{"class":163},[112,10391,425],{"class":129},[112,10393,10394],{"class":136},"'User ID not returned from Auth0'",[112,10396,431],{"class":129},[112,10398,10399],{"class":114,"line":3742},[112,10400,3643],{"class":129},[112,10402,10403],{"class":114,"line":3747},[112,10404,147],{"emptyLinePlaceholder":146},[112,10406,10407,10409],{"class":114,"line":3752},[112,10408,5009],{"class":125},[112,10410,1294],{"class":129},[112,10412,10413],{"class":114,"line":3758},[112,10414,10415],{"class":129},"        userId: user.user_id,\n",[112,10417,10418,10421,10423],{"class":114,"line":3778},[112,10419,10420],{"class":129},"        email: user.email",[112,10422,3948],{"class":125},[112,10424,179],{"class":129},[112,10426,10427],{"class":114,"line":3787},[112,10428,3643],{"class":129},[112,10430,10431,10434,10436],{"class":114,"line":3809},[112,10432,10433],{"class":129},"    } ",[112,10435,3686],{"class":125},[112,10437,3689],{"class":129},[112,10439,10440,10442,10444,10447],{"class":114,"line":3827},[112,10441,8124],{"class":156},[112,10443,2124],{"class":129},[112,10445,10446],{"class":163},"handleAuth0Error",[112,10448,10449],{"class":129},"(error)\n",[112,10451,10452],{"class":114,"line":3834},[112,10453,3118],{"class":129},[112,10455,10456],{"class":114,"line":3848},[112,10457,3232],{"class":129},[112,10459,10460],{"class":114,"line":3858},[112,10461,147],{"emptyLinePlaceholder":146},[112,10463,10464,10466,10469,10471,10474,10476,10479,10481,10483,10485,10487,10490],{"class":114,"line":3863},[112,10465,10270],{"class":125},[112,10467,10468],{"class":163}," getUserById",[112,10470,425],{"class":129},[112,10472,10473],{"class":222},"userId",[112,10475,2243],{"class":125},[112,10477,10478],{"class":156}," string",[112,10480,10186],{"class":129},[112,10482,2243],{"class":125},[112,10484,10289],{"class":163},[112,10486,6944],{"class":129},[112,10488,10489],{"class":163},"User",[112,10491,10297],{"class":129},[112,10493,10494,10496],{"class":114,"line":3874},[112,10495,10302],{"class":125},[112,10497,1294],{"class":129},[112,10499,10500,10502,10504,10506,10508,10511],{"class":114,"line":3879},[112,10501,5009],{"class":125},[112,10503,419],{"class":125},[112,10505,10318],{"class":156},[112,10507,10321],{"class":129},[112,10509,10510],{"class":163},"getUser",[112,10512,10513],{"class":129},"({ id: userId })\n",[112,10515,10516,10518,10520],{"class":114,"line":3884},[112,10517,10433],{"class":129},[112,10519,3686],{"class":125},[112,10521,3689],{"class":129},[112,10523,10524,10526,10528,10530],{"class":114,"line":3907},[112,10525,8124],{"class":156},[112,10527,2124],{"class":129},[112,10529,10446],{"class":163},[112,10531,10449],{"class":129},[112,10533,10534],{"class":114,"line":3922},[112,10535,3118],{"class":129},[112,10537,10538],{"class":114,"line":3935},[112,10539,3232],{"class":129},[112,10541,10542],{"class":114,"line":3940},[112,10543,147],{"emptyLinePlaceholder":146},[112,10545,10546,10548,10551,10553,10555,10557,10559,10561,10563,10565,10568,10570,10572,10575,10577,10579,10581,10583],{"class":114,"line":3954},[112,10547,10270],{"class":125},[112,10549,10550],{"class":163}," updateUser",[112,10552,425],{"class":129},[112,10554,10473],{"class":222},[112,10556,2243],{"class":125},[112,10558,10478],{"class":156},[112,10560,447],{"class":129},[112,10562,564],{"class":222},[112,10564,2243],{"class":125},[112,10566,10567],{"class":163}," Partial",[112,10569,6944],{"class":129},[112,10571,10489],{"class":163},[112,10573,10574],{"class":129},">)",[112,10576,2243],{"class":125},[112,10578,10289],{"class":163},[112,10580,6944],{"class":129},[112,10582,10489],{"class":163},[112,10584,10297],{"class":129},[112,10586,10587,10589],{"class":114,"line":3970},[112,10588,10302],{"class":125},[112,10590,1294],{"class":129},[112,10592,10593,10595,10597,10599,10601,10604],{"class":114,"line":3978},[112,10594,5009],{"class":125},[112,10596,419],{"class":125},[112,10598,10318],{"class":156},[112,10600,10321],{"class":129},[112,10602,10603],{"class":163},"updateUser",[112,10605,10606],{"class":129},"({ id: userId }, data)\n",[112,10608,10609,10611,10613],{"class":114,"line":3983},[112,10610,10433],{"class":129},[112,10612,3686],{"class":125},[112,10614,3689],{"class":129},[112,10616,10617,10619,10621,10623],{"class":114,"line":3988},[112,10618,8124],{"class":156},[112,10620,2124],{"class":129},[112,10622,10446],{"class":163},[112,10624,10449],{"class":129},[112,10626,10627],{"class":114,"line":4000},[112,10628,3118],{"class":129},[112,10630,10631],{"class":114,"line":4016},[112,10632,3232],{"class":129},[112,10634,10635],{"class":114,"line":4023},[112,10636,147],{"emptyLinePlaceholder":146},[112,10638,10639,10641,10644,10646,10648,10650,10652,10654,10656,10658,10660,10663],{"class":114,"line":4028},[112,10640,10270],{"class":125},[112,10642,10643],{"class":163}," deleteUser",[112,10645,425],{"class":129},[112,10647,10473],{"class":222},[112,10649,2243],{"class":125},[112,10651,10478],{"class":156},[112,10653,10186],{"class":129},[112,10655,2243],{"class":125},[112,10657,10289],{"class":163},[112,10659,6944],{"class":129},[112,10661,10662],{"class":156},"void",[112,10664,10297],{"class":129},[112,10666,10667,10669],{"class":114,"line":4033},[112,10668,10302],{"class":125},[112,10670,1294],{"class":129},[112,10672,10673,10675,10677,10679,10682],{"class":114,"line":4040},[112,10674,3837],{"class":125},[112,10676,10318],{"class":156},[112,10678,10321],{"class":129},[112,10680,10681],{"class":163},"deleteUser",[112,10683,10513],{"class":129},[112,10685,10686,10688,10690],{"class":114,"line":4052},[112,10687,10433],{"class":129},[112,10689,3686],{"class":125},[112,10691,3689],{"class":129},[112,10693,10694,10696,10698,10700],{"class":114,"line":4069},[112,10695,8124],{"class":156},[112,10697,2124],{"class":129},[112,10699,10446],{"class":163},[112,10701,10449],{"class":129},[112,10703,10704],{"class":114,"line":4078},[112,10705,3118],{"class":129},[112,10707,10708],{"class":114,"line":4096},[112,10709,3232],{"class":129},[112,10711,10712],{"class":114,"line":4101},[112,10713,147],{"emptyLinePlaceholder":146},[112,10715,10716,10718,10721,10723,10725,10727,10730,10732,10734,10737],{"class":114,"line":5771},[112,10717,10094],{"class":125},[112,10719,10720],{"class":163}," handleAuth0Error",[112,10722,425],{"class":129},[112,10724,4878],{"class":222},[112,10726,2243],{"class":125},[112,10728,10729],{"class":156}," any",[112,10731,10186],{"class":129},[112,10733,2243],{"class":125},[112,10735,10736],{"class":156}," never",[112,10738,1294],{"class":129},[112,10740,10741,10743,10746,10748,10751],{"class":114,"line":5776},[112,10742,3511],{"class":125},[112,10744,10745],{"class":129}," (error.statusCode ",[112,10747,3624],{"class":125},[112,10749,10750],{"class":156}," 409",[112,10752,1969],{"class":129},[112,10754,10755,10757,10759,10762,10764,10767],{"class":114,"line":5781},[112,10756,3519],{"class":125},[112,10758,232],{"class":125},[112,10760,10761],{"class":163}," InternalServerErrorException",[112,10763,425],{"class":129},[112,10765,10766],{"class":136},"'User already exists'",[112,10768,431],{"class":129},[112,10770,10771],{"class":114,"line":5788},[112,10772,3118],{"class":129},[112,10774,10775],{"class":114,"line":5793},[112,10776,147],{"emptyLinePlaceholder":146},[112,10778,10779,10781,10783,10785,10788],{"class":114,"line":5809},[112,10780,3511],{"class":125},[112,10782,10745],{"class":129},[112,10784,3624],{"class":125},[112,10786,10787],{"class":156}," 400",[112,10789,1969],{"class":129},[112,10791,10792,10794,10796,10798],{"class":114,"line":5814},[112,10793,3519],{"class":125},[112,10795,232],{"class":125},[112,10797,10761],{"class":163},[112,10799,2304],{"class":129},[112,10801,10802,10805,10807,10809,10812,10814],{"class":114,"line":5826},[112,10803,10804],{"class":136},"        `Invalid input: ${",[112,10806,4878],{"class":129},[112,10808,2124],{"class":136},[112,10810,10811],{"class":129},"message",[112,10813,592],{"class":136},[112,10815,179],{"class":129},[112,10817,10818],{"class":114,"line":5842},[112,10819,10820],{"class":129},"      )\n",[112,10822,10823],{"class":114,"line":5847},[112,10824,3118],{"class":129},[112,10826,10827],{"class":114,"line":5852},[112,10828,147],{"emptyLinePlaceholder":146},[112,10830,10831,10833,10835,10837],{"class":114,"line":5860},[112,10832,4966],{"class":125},[112,10834,232],{"class":125},[112,10836,10761],{"class":163},[112,10838,2304],{"class":129},[112,10840,10841,10844,10846,10848,10850,10853,10856],{"class":114,"line":5865},[112,10842,10843],{"class":136},"      `Auth0 error: ${",[112,10845,4878],{"class":129},[112,10847,2124],{"class":136},[112,10849,10811],{"class":129},[112,10851,10852],{"class":125}," ||",[112,10854,10855],{"class":136}," 'Unknown error'}`",[112,10857,179],{"class":129},[112,10859,10860],{"class":114,"line":5870},[112,10861,10862],{"class":129},"    )\n",[112,10864,10865],{"class":114,"line":5876},[112,10866,3232],{"class":129},[112,10868,10869],{"class":114,"line":5905},[112,10870,1452],{"class":129},[11,10872,10874],{"id":10873},"rest-apiでの実装例","REST APIでの実装例",[83,10876,10878],{"id":10877},"controller","Controller",[103,10880,10882],{"className":105,"code":10881,"language":107,"meta":108,"style":108},"import {\n  Body,\n  Controller,\n  Delete,\n  Get,\n  Param,\n  Post,\n  Put,\n} from '@nestjs\u002Fcommon'\nimport { Auth0Service, CreateUserInput } from '.\u002Fauth0.service'\n\nexport class CreateUserDto {\n  email: string\n  password: string\n  name?: string\n}\n\nexport class UpdateUserDto {\n  name?: string\n  email?: string\n}\n\n@Controller('users')\nexport class UsersController {\n  constructor(private readonly auth0Service: Auth0Service) {}\n\n  @Post()\n  async createUser(@Body() dto: CreateUserDto) {\n    return this.auth0Service.createUser(dto)\n  }\n\n  @Get(':userId')\n  async getUser(@Param('userId') userId: string) {\n    return this.auth0Service.getUserById(userId)\n  }\n\n  @Put(':userId')\n  async updateUser(\n    @Param('userId') userId: string,\n    @Body() dto: UpdateUserDto,\n  ) {\n    return this.auth0Service.updateUser(userId, dto)\n  }\n\n  @Delete(':userId')\n  async deleteUser(@Param('userId') userId: string) {\n    await this.auth0Service.deleteUser(userId)\n    return { message: 'User deleted successfully' }\n  }\n}\n",[90,10883,10884,10890,10895,10900,10905,10910,10915,10920,10925,10934,10945,10949,10960,10968,10976,10984,10988,10992,11003,11011,11019,11023,11027,11040,11051,11071,11075,11085,11108,11122,11126,11130,11144,11171,11185,11189,11193,11206,11214,11235,11251,11256,11269,11273,11277,11290,11314,11326,11338,11342],{"__ignoreMap":108},[112,10885,10886,10888],{"class":114,"line":115},[112,10887,126],{"class":125},[112,10889,1294],{"class":129},[112,10891,10892],{"class":114,"line":122},[112,10893,10894],{"class":129},"  Body,\n",[112,10896,10897],{"class":114,"line":143},[112,10898,10899],{"class":129},"  Controller,\n",[112,10901,10902],{"class":114,"line":150},[112,10903,10904],{"class":129},"  Delete,\n",[112,10906,10907],{"class":114,"line":170},[112,10908,10909],{"class":129},"  Get,\n",[112,10911,10912],{"class":114,"line":182},[112,10913,10914],{"class":129},"  Param,\n",[112,10916,10917],{"class":114,"line":193},[112,10918,10919],{"class":129},"  Post,\n",[112,10921,10922],{"class":114,"line":205},[112,10923,10924],{"class":129},"  Put,\n",[112,10926,10927,10930,10932],{"class":114,"line":241},[112,10928,10929],{"class":129},"} ",[112,10931,133],{"class":125},[112,10933,9868],{"class":136},[112,10935,10936,10938,10941,10943],{"class":114,"line":247},[112,10937,126],{"class":125},[112,10939,10940],{"class":129}," { Auth0Service, CreateUserInput } ",[112,10942,133],{"class":125},[112,10944,9892],{"class":136},[112,10946,10947],{"class":114,"line":351},[112,10948,147],{"emptyLinePlaceholder":146},[112,10950,10951,10953,10955,10958],{"class":114,"line":361},[112,10952,288],{"class":125},[112,10954,9931],{"class":125},[112,10956,10957],{"class":163}," CreateUserDto",[112,10959,1294],{"class":129},[112,10961,10962,10964,10966],{"class":114,"line":367},[112,10963,10001],{"class":222},[112,10965,2243],{"class":125},[112,10967,10006],{"class":156},[112,10969,10970,10972,10974],{"class":114,"line":373},[112,10971,10011],{"class":222},[112,10973,2243],{"class":125},[112,10975,10006],{"class":156},[112,10977,10978,10980,10982],{"class":114,"line":379},[112,10979,10020],{"class":222},[112,10981,10023],{"class":125},[112,10983,10006],{"class":156},[112,10985,10986],{"class":114,"line":1302},[112,10987,1452],{"class":129},[112,10989,10990],{"class":114,"line":1502},[112,10991,147],{"emptyLinePlaceholder":146},[112,10993,10994,10996,10998,11001],{"class":114,"line":1507},[112,10995,288],{"class":125},[112,10997,9931],{"class":125},[112,10999,11000],{"class":163}," UpdateUserDto",[112,11002,1294],{"class":129},[112,11004,11005,11007,11009],{"class":114,"line":1512},[112,11006,10020],{"class":222},[112,11008,10023],{"class":125},[112,11010,10006],{"class":156},[112,11012,11013,11015,11017],{"class":114,"line":1518},[112,11014,10001],{"class":222},[112,11016,10023],{"class":125},[112,11018,10006],{"class":156},[112,11020,11021],{"class":114,"line":1524},[112,11022,1452],{"class":129},[112,11024,11025],{"class":114,"line":1530},[112,11026,147],{"emptyLinePlaceholder":146},[112,11028,11029,11031,11033,11035,11038],{"class":114,"line":1536},[112,11030,9901],{"class":129},[112,11032,10878],{"class":163},[112,11034,425],{"class":129},[112,11036,11037],{"class":136},"'users'",[112,11039,431],{"class":129},[112,11041,11042,11044,11046,11049],{"class":114,"line":1542},[112,11043,288],{"class":125},[112,11045,9931],{"class":125},[112,11047,11048],{"class":163}," UsersController",[112,11050,1294],{"class":129},[112,11052,11053,11055,11057,11059,11061,11064,11066,11068],{"class":114,"line":2402},[112,11054,10128],{"class":125},[112,11056,425],{"class":129},[112,11058,10133],{"class":125},[112,11060,10097],{"class":125},[112,11062,11063],{"class":222}," auth0Service",[112,11065,2243],{"class":125},[112,11067,10087],{"class":163},[112,11069,11070],{"class":129},") {}\n",[112,11072,11073],{"class":114,"line":2407},[112,11074,147],{"emptyLinePlaceholder":146},[112,11076,11077,11080,11083],{"class":114,"line":2413},[112,11078,11079],{"class":129},"  @",[112,11081,11082],{"class":163},"Post",[112,11084,630],{"class":129},[112,11086,11087,11089,11091,11094,11097,11099,11102,11104,11106],{"class":114,"line":2446},[112,11088,10270],{"class":125},[112,11090,10273],{"class":163},[112,11092,11093],{"class":129},"(@",[112,11095,11096],{"class":163},"Body",[112,11098,1392],{"class":129},[112,11100,11101],{"class":222},"dto",[112,11103,2243],{"class":125},[112,11105,10957],{"class":163},[112,11107,1969],{"class":129},[112,11109,11110,11112,11114,11117,11119],{"class":114,"line":2451},[112,11111,3973],{"class":125},[112,11113,10318],{"class":156},[112,11115,11116],{"class":129},".auth0Service.",[112,11118,10324],{"class":163},[112,11120,11121],{"class":129},"(dto)\n",[112,11123,11124],{"class":114,"line":2464},[112,11125,3232],{"class":129},[112,11127,11128],{"class":114,"line":2481},[112,11129,147],{"emptyLinePlaceholder":146},[112,11131,11132,11134,11137,11139,11142],{"class":114,"line":2486},[112,11133,11079],{"class":129},[112,11135,11136],{"class":163},"Get",[112,11138,425],{"class":129},[112,11140,11141],{"class":136},"':userId'",[112,11143,431],{"class":129},[112,11145,11146,11148,11151,11153,11156,11158,11161,11163,11165,11167,11169],{"class":114,"line":3229},[112,11147,10270],{"class":125},[112,11149,11150],{"class":163}," getUser",[112,11152,11093],{"class":129},[112,11154,11155],{"class":163},"Param",[112,11157,425],{"class":129},[112,11159,11160],{"class":136},"'userId'",[112,11162,226],{"class":129},[112,11164,10473],{"class":222},[112,11166,2243],{"class":125},[112,11168,10478],{"class":156},[112,11170,1969],{"class":129},[112,11172,11173,11175,11177,11179,11182],{"class":114,"line":3235},[112,11174,3973],{"class":125},[112,11176,10318],{"class":156},[112,11178,11116],{"class":129},[112,11180,11181],{"class":163},"getUserById",[112,11183,11184],{"class":129},"(userId)\n",[112,11186,11187],{"class":114,"line":3657},[112,11188,3232],{"class":129},[112,11190,11191],{"class":114,"line":3662},[112,11192,147],{"emptyLinePlaceholder":146},[112,11194,11195,11197,11200,11202,11204],{"class":114,"line":3667},[112,11196,11079],{"class":129},[112,11198,11199],{"class":163},"Put",[112,11201,425],{"class":129},[112,11203,11141],{"class":136},[112,11205,431],{"class":129},[112,11207,11208,11210,11212],{"class":114,"line":3680},[112,11209,10270],{"class":125},[112,11211,10550],{"class":163},[112,11213,2304],{"class":129},[112,11215,11216,11219,11221,11223,11225,11227,11229,11231,11233],{"class":114,"line":3692},[112,11217,11218],{"class":129},"    @",[112,11220,11155],{"class":163},[112,11222,425],{"class":129},[112,11224,11160],{"class":136},[112,11226,226],{"class":129},[112,11228,10473],{"class":222},[112,11230,2243],{"class":125},[112,11232,10478],{"class":156},[112,11234,179],{"class":129},[112,11236,11237,11239,11241,11243,11245,11247,11249],{"class":114,"line":3716},[112,11238,11218],{"class":129},[112,11240,11096],{"class":163},[112,11242,1392],{"class":129},[112,11244,11101],{"class":222},[112,11246,2243],{"class":125},[112,11248,11000],{"class":163},[112,11250,179],{"class":129},[112,11252,11253],{"class":114,"line":3737},[112,11254,11255],{"class":129},"  ) {\n",[112,11257,11258,11260,11262,11264,11266],{"class":114,"line":3742},[112,11259,3973],{"class":125},[112,11261,10318],{"class":156},[112,11263,11116],{"class":129},[112,11265,10603],{"class":163},[112,11267,11268],{"class":129},"(userId, dto)\n",[112,11270,11271],{"class":114,"line":3747},[112,11272,3232],{"class":129},[112,11274,11275],{"class":114,"line":3752},[112,11276,147],{"emptyLinePlaceholder":146},[112,11278,11279,11281,11284,11286,11288],{"class":114,"line":3758},[112,11280,11079],{"class":129},[112,11282,11283],{"class":163},"Delete",[112,11285,425],{"class":129},[112,11287,11141],{"class":136},[112,11289,431],{"class":129},[112,11291,11292,11294,11296,11298,11300,11302,11304,11306,11308,11310,11312],{"class":114,"line":3778},[112,11293,10270],{"class":125},[112,11295,10643],{"class":163},[112,11297,11093],{"class":129},[112,11299,11155],{"class":163},[112,11301,425],{"class":129},[112,11303,11160],{"class":136},[112,11305,226],{"class":129},[112,11307,10473],{"class":222},[112,11309,2243],{"class":125},[112,11311,10478],{"class":156},[112,11313,1969],{"class":129},[112,11315,11316,11318,11320,11322,11324],{"class":114,"line":3787},[112,11317,3385],{"class":125},[112,11319,10318],{"class":156},[112,11321,11116],{"class":129},[112,11323,10681],{"class":163},[112,11325,11184],{"class":129},[112,11327,11328,11330,11333,11336],{"class":114,"line":3809},[112,11329,3973],{"class":125},[112,11331,11332],{"class":129}," { message: ",[112,11334,11335],{"class":136},"'User deleted successfully'",[112,11337,2395],{"class":129},[112,11339,11340],{"class":114,"line":3827},[112,11341,3232],{"class":129},[112,11343,11344],{"class":114,"line":3834},[112,11345,1452],{"class":129},[83,11347,11349],{"id":11348},"dtoのバリデーション","DTOのバリデーション",[103,11351,11353],{"className":105,"code":11352,"language":107,"meta":108,"style":108},"import { IsEmail, IsString, MinLength, IsOptional } from 'class-validator'\n\nexport class CreateUserDto {\n  @IsEmail()\n  email: string\n\n  @IsString()\n  @MinLength(8)\n  password: string\n\n  @IsString()\n  @IsOptional()\n  name?: string\n}\n",[90,11354,11355,11367,11371,11381,11390,11398,11402,11411,11425,11433,11437,11445,11454,11462],{"__ignoreMap":108},[112,11356,11357,11359,11362,11364],{"class":114,"line":115},[112,11358,126],{"class":125},[112,11360,11361],{"class":129}," { IsEmail, IsString, MinLength, IsOptional } ",[112,11363,133],{"class":125},[112,11365,11366],{"class":136}," 'class-validator'\n",[112,11368,11369],{"class":114,"line":122},[112,11370,147],{"emptyLinePlaceholder":146},[112,11372,11373,11375,11377,11379],{"class":114,"line":143},[112,11374,288],{"class":125},[112,11376,9931],{"class":125},[112,11378,10957],{"class":163},[112,11380,1294],{"class":129},[112,11382,11383,11385,11388],{"class":114,"line":150},[112,11384,11079],{"class":129},[112,11386,11387],{"class":163},"IsEmail",[112,11389,630],{"class":129},[112,11391,11392,11394,11396],{"class":114,"line":170},[112,11393,10001],{"class":222},[112,11395,2243],{"class":125},[112,11397,10006],{"class":156},[112,11399,11400],{"class":114,"line":182},[112,11401,147],{"emptyLinePlaceholder":146},[112,11403,11404,11406,11409],{"class":114,"line":193},[112,11405,11079],{"class":129},[112,11407,11408],{"class":163},"IsString",[112,11410,630],{"class":129},[112,11412,11413,11415,11418,11420,11423],{"class":114,"line":205},[112,11414,11079],{"class":129},[112,11416,11417],{"class":163},"MinLength",[112,11419,425],{"class":129},[112,11421,11422],{"class":156},"8",[112,11424,431],{"class":129},[112,11426,11427,11429,11431],{"class":114,"line":241},[112,11428,10011],{"class":222},[112,11430,2243],{"class":125},[112,11432,10006],{"class":156},[112,11434,11435],{"class":114,"line":247},[112,11436,147],{"emptyLinePlaceholder":146},[112,11438,11439,11441,11443],{"class":114,"line":351},[112,11440,11079],{"class":129},[112,11442,11408],{"class":163},[112,11444,630],{"class":129},[112,11446,11447,11449,11452],{"class":114,"line":361},[112,11448,11079],{"class":129},[112,11450,11451],{"class":163},"IsOptional",[112,11453,630],{"class":129},[112,11455,11456,11458,11460],{"class":114,"line":367},[112,11457,10020],{"class":222},[112,11459,10023],{"class":125},[112,11461,10006],{"class":156},[112,11463,11464],{"class":114,"line":373},[112,11465,1452],{"class":129},[103,11467,11469],{"className":771,"code":11468,"language":773,"meta":108,"style":108},"npm install class-validator class-transformer\n",[90,11470,11471],{"__ignoreMap":108},[112,11472,11473,11475,11477,11480],{"class":114,"line":115},[112,11474,6832],{"class":163},[112,11476,7883],{"class":136},[112,11478,11479],{"class":136}," class-validator",[112,11481,11482],{"class":136}," class-transformer\n",[11,11484,11486],{"id":11485},"graphql-apiでの実装例","GraphQL APIでの実装例",[83,11488,11490],{"id":11489},"types","Types",[103,11492,11494],{"className":105,"code":11493,"language":107,"meta":108,"style":108},"import { Field, InputType, ObjectType } from '@nestjs\u002Fgraphql'\n\n@InputType()\nexport class CreateUserInput {\n  @Field()\n  email: string\n\n  @Field()\n  password: string\n\n  @Field({ nullable: true })\n  name?: string\n}\n\n@ObjectType()\nexport class CreateUserOutput {\n  @Field()\n  userId: string\n\n  @Field()\n  email: string\n}\n",[90,11495,11496,11508,11512,11521,11531,11540,11548,11552,11560,11568,11572,11586,11594,11598,11602,11611,11621,11629,11637,11641,11649,11657],{"__ignoreMap":108},[112,11497,11498,11500,11503,11505],{"class":114,"line":115},[112,11499,126],{"class":125},[112,11501,11502],{"class":129}," { Field, InputType, ObjectType } ",[112,11504,133],{"class":125},[112,11506,11507],{"class":136}," '@nestjs\u002Fgraphql'\n",[112,11509,11510],{"class":114,"line":122},[112,11511,147],{"emptyLinePlaceholder":146},[112,11513,11514,11516,11519],{"class":114,"line":143},[112,11515,9901],{"class":129},[112,11517,11518],{"class":163},"InputType",[112,11520,630],{"class":129},[112,11522,11523,11525,11527,11529],{"class":114,"line":150},[112,11524,288],{"class":125},[112,11526,9931],{"class":125},[112,11528,9994],{"class":163},[112,11530,1294],{"class":129},[112,11532,11533,11535,11538],{"class":114,"line":170},[112,11534,11079],{"class":129},[112,11536,11537],{"class":163},"Field",[112,11539,630],{"class":129},[112,11541,11542,11544,11546],{"class":114,"line":182},[112,11543,10001],{"class":222},[112,11545,2243],{"class":125},[112,11547,10006],{"class":156},[112,11549,11550],{"class":114,"line":193},[112,11551,147],{"emptyLinePlaceholder":146},[112,11553,11554,11556,11558],{"class":114,"line":205},[112,11555,11079],{"class":129},[112,11557,11537],{"class":163},[112,11559,630],{"class":129},[112,11561,11562,11564,11566],{"class":114,"line":241},[112,11563,10011],{"class":222},[112,11565,2243],{"class":125},[112,11567,10006],{"class":156},[112,11569,11570],{"class":114,"line":247},[112,11571,147],{"emptyLinePlaceholder":146},[112,11573,11574,11576,11578,11581,11583],{"class":114,"line":351},[112,11575,11079],{"class":129},[112,11577,11537],{"class":163},[112,11579,11580],{"class":129},"({ nullable: ",[112,11582,4345],{"class":156},[112,11584,11585],{"class":129}," })\n",[112,11587,11588,11590,11592],{"class":114,"line":361},[112,11589,10020],{"class":222},[112,11591,10023],{"class":125},[112,11593,10006],{"class":156},[112,11595,11596],{"class":114,"line":367},[112,11597,1452],{"class":129},[112,11599,11600],{"class":114,"line":373},[112,11601,147],{"emptyLinePlaceholder":146},[112,11603,11604,11606,11609],{"class":114,"line":379},[112,11605,9901],{"class":129},[112,11607,11608],{"class":163},"ObjectType",[112,11610,630],{"class":129},[112,11612,11613,11615,11617,11619],{"class":114,"line":1302},[112,11614,288],{"class":125},[112,11616,9931],{"class":125},[112,11618,10042],{"class":163},[112,11620,1294],{"class":129},[112,11622,11623,11625,11627],{"class":114,"line":1502},[112,11624,11079],{"class":129},[112,11626,11537],{"class":163},[112,11628,630],{"class":129},[112,11630,11631,11633,11635],{"class":114,"line":1507},[112,11632,10049],{"class":222},[112,11634,2243],{"class":125},[112,11636,10006],{"class":156},[112,11638,11639],{"class":114,"line":1512},[112,11640,147],{"emptyLinePlaceholder":146},[112,11642,11643,11645,11647],{"class":114,"line":1518},[112,11644,11079],{"class":129},[112,11646,11537],{"class":163},[112,11648,630],{"class":129},[112,11650,11651,11653,11655],{"class":114,"line":1524},[112,11652,10001],{"class":222},[112,11654,2243],{"class":125},[112,11656,10006],{"class":156},[112,11658,11659],{"class":114,"line":1530},[112,11660,1452],{"class":129},[83,11662,11664],{"id":11663},"resolver","Resolver",[103,11666,11668],{"className":105,"code":11667,"language":107,"meta":108,"style":108},"import { Args, Mutation, Query, Resolver } from '@nestjs\u002Fgraphql'\nimport { Auth0Service } from '..\u002Fauth0\u002Fauth0.service'\nimport { CreateUserInput, CreateUserOutput } from '.\u002Fuser.types'\n\n@Resolver()\nexport class UsersResolver {\n  constructor(private readonly auth0Service: Auth0Service) {}\n\n  @Mutation(() => CreateUserOutput)\n  async createUser(\n    @Args('input') input: CreateUserInput,\n  ): Promise\u003CCreateUserOutput> {\n    return this.auth0Service.createUser(input)\n  }\n\n  @Query(() => String)\n  async getUserById(@Args('userId') userId: string) {\n    const user = await this.auth0Service.getUserById(userId)\n    return JSON.stringify(user)\n  }\n}\n",[90,11669,11670,11681,11692,11704,11708,11716,11727,11745,11749,11764,11772,11793,11808,11821,11825,11829,11843,11867,11885,11899,11903],{"__ignoreMap":108},[112,11671,11672,11674,11677,11679],{"class":114,"line":115},[112,11673,126],{"class":125},[112,11675,11676],{"class":129}," { Args, Mutation, Query, Resolver } ",[112,11678,133],{"class":125},[112,11680,11507],{"class":136},[112,11682,11683,11685,11687,11689],{"class":114,"line":122},[112,11684,126],{"class":125},[112,11686,9887],{"class":129},[112,11688,133],{"class":125},[112,11690,11691],{"class":136}," '..\u002Fauth0\u002Fauth0.service'\n",[112,11693,11694,11696,11699,11701],{"class":114,"line":143},[112,11695,126],{"class":125},[112,11697,11698],{"class":129}," { CreateUserInput, CreateUserOutput } ",[112,11700,133],{"class":125},[112,11702,11703],{"class":136}," '.\u002Fuser.types'\n",[112,11705,11706],{"class":114,"line":150},[112,11707,147],{"emptyLinePlaceholder":146},[112,11709,11710,11712,11714],{"class":114,"line":170},[112,11711,9901],{"class":129},[112,11713,11664],{"class":163},[112,11715,630],{"class":129},[112,11717,11718,11720,11722,11725],{"class":114,"line":182},[112,11719,288],{"class":125},[112,11721,9931],{"class":125},[112,11723,11724],{"class":163}," UsersResolver",[112,11726,1294],{"class":129},[112,11728,11729,11731,11733,11735,11737,11739,11741,11743],{"class":114,"line":193},[112,11730,10128],{"class":125},[112,11732,425],{"class":129},[112,11734,10133],{"class":125},[112,11736,10097],{"class":125},[112,11738,11063],{"class":222},[112,11740,2243],{"class":125},[112,11742,10087],{"class":163},[112,11744,11070],{"class":129},[112,11746,11747],{"class":114,"line":205},[112,11748,147],{"emptyLinePlaceholder":146},[112,11750,11751,11753,11756,11759,11761],{"class":114,"line":241},[112,11752,11079],{"class":129},[112,11754,11755],{"class":163},"Mutation",[112,11757,11758],{"class":129},"(() ",[112,11760,229],{"class":125},[112,11762,11763],{"class":129}," CreateUserOutput)\n",[112,11765,11766,11768,11770],{"class":114,"line":247},[112,11767,10270],{"class":125},[112,11769,10273],{"class":163},[112,11771,2304],{"class":129},[112,11773,11774,11776,11779,11781,11783,11785,11787,11789,11791],{"class":114,"line":351},[112,11775,11218],{"class":129},[112,11777,11778],{"class":163},"Args",[112,11780,425],{"class":129},[112,11782,3769],{"class":136},[112,11784,226],{"class":129},[112,11786,10278],{"class":222},[112,11788,2243],{"class":125},[112,11790,9994],{"class":163},[112,11792,179],{"class":129},[112,11794,11795,11798,11800,11802,11804,11806],{"class":114,"line":361},[112,11796,11797],{"class":129},"  )",[112,11799,2243],{"class":125},[112,11801,10289],{"class":163},[112,11803,6944],{"class":129},[112,11805,10294],{"class":163},[112,11807,10297],{"class":129},[112,11809,11810,11812,11814,11816,11818],{"class":114,"line":367},[112,11811,3973],{"class":125},[112,11813,10318],{"class":156},[112,11815,11116],{"class":129},[112,11817,10324],{"class":163},[112,11819,11820],{"class":129},"(input)\n",[112,11822,11823],{"class":114,"line":373},[112,11824,3232],{"class":129},[112,11826,11827],{"class":114,"line":379},[112,11828,147],{"emptyLinePlaceholder":146},[112,11830,11831,11833,11836,11838,11840],{"class":114,"line":1302},[112,11832,11079],{"class":129},[112,11834,11835],{"class":163},"Query",[112,11837,11758],{"class":129},[112,11839,229],{"class":125},[112,11841,11842],{"class":129}," String)\n",[112,11844,11845,11847,11849,11851,11853,11855,11857,11859,11861,11863,11865],{"class":114,"line":1502},[112,11846,10270],{"class":125},[112,11848,10468],{"class":163},[112,11850,11093],{"class":129},[112,11852,11778],{"class":163},[112,11854,425],{"class":129},[112,11856,11160],{"class":136},[112,11858,226],{"class":129},[112,11860,10473],{"class":222},[112,11862,2243],{"class":125},[112,11864,10478],{"class":156},[112,11866,1969],{"class":129},[112,11868,11869,11871,11873,11875,11877,11879,11881,11883],{"class":114,"line":1507},[112,11870,3472],{"class":125},[112,11872,10311],{"class":156},[112,11874,160],{"class":125},[112,11876,419],{"class":125},[112,11878,10318],{"class":156},[112,11880,11116],{"class":129},[112,11882,11181],{"class":163},[112,11884,11184],{"class":129},[112,11886,11887,11889,11892,11894,11896],{"class":114,"line":1512},[112,11888,3973],{"class":125},[112,11890,11891],{"class":156}," JSON",[112,11893,2124],{"class":129},[112,11895,5294],{"class":163},[112,11897,11898],{"class":129},"(user)\n",[112,11900,11901],{"class":114,"line":1518},[112,11902,3232],{"class":129},[112,11904,11905],{"class":114,"line":1524},[112,11906,1452],{"class":129},[83,11908,11910],{"id":11909},"graphqlクエリ例","GraphQLクエリ例",[103,11912,11916],{"className":11913,"code":11914,"language":11915,"meta":108,"style":108},"language-graphql shiki shiki-themes github-light github-dark","# ユーザー作成\nmutation CreateUser {\n  createUser(\n    input: {\n      email: \"user@example.com\"\n      password: \"SecurePassword123!\"\n      name: \"John Doe\"\n    }\n  ) {\n    userId\n    email\n  }\n}\n\n# ユーザー取得\nquery GetUser {\n  getUserById(userId: \"auth0|123456789\")\n}\n","graphql",[90,11917,11918,11923,11928,11933,11938,11943,11948,11953,11957,11961,11966,11971,11975,11979,11983,11988,11993,11998],{"__ignoreMap":108},[112,11919,11920],{"class":114,"line":115},[112,11921,11922],{},"# ユーザー作成\n",[112,11924,11925],{"class":114,"line":122},[112,11926,11927],{},"mutation CreateUser {\n",[112,11929,11930],{"class":114,"line":143},[112,11931,11932],{},"  createUser(\n",[112,11934,11935],{"class":114,"line":150},[112,11936,11937],{},"    input: {\n",[112,11939,11940],{"class":114,"line":170},[112,11941,11942],{},"      email: \"user@example.com\"\n",[112,11944,11945],{"class":114,"line":182},[112,11946,11947],{},"      password: \"SecurePassword123!\"\n",[112,11949,11950],{"class":114,"line":193},[112,11951,11952],{},"      name: \"John Doe\"\n",[112,11954,11955],{"class":114,"line":205},[112,11956,3118],{},[112,11958,11959],{"class":114,"line":241},[112,11960,11255],{},[112,11962,11963],{"class":114,"line":247},[112,11964,11965],{},"    userId\n",[112,11967,11968],{"class":114,"line":351},[112,11969,11970],{},"    email\n",[112,11972,11973],{"class":114,"line":361},[112,11974,3232],{},[112,11976,11977],{"class":114,"line":367},[112,11978,1452],{},[112,11980,11981],{"class":114,"line":373},[112,11982,147],{"emptyLinePlaceholder":146},[112,11984,11985],{"class":114,"line":379},[112,11986,11987],{},"# ユーザー取得\n",[112,11989,11990],{"class":114,"line":1302},[112,11991,11992],{},"query GetUser {\n",[112,11994,11995],{"class":114,"line":1502},[112,11996,11997],{},"  getUserById(userId: \"auth0|123456789\")\n",[112,11999,12000],{"class":114,"line":1507},[112,12001,1452],{},[11,12003,12004],{"id":12004},"セキュリティのベストプラクティス",[83,12006,12008],{"id":12007},"_1-パスワードポリシー","1. パスワードポリシー",[15,12010,12011],{},"Auth0側でパスワードポリシーを設定します",[31,12013,12014,12017,12020],{},[34,12015,12016],{},"最小8文字以上",[34,12018,12019],{},"大文字・小文字・数字・記号を含む",[34,12021,12022],{},"一般的なパスワードを禁止",[83,12024,12026],{"id":12025},"_2-環境変数の管理","2. 環境変数の管理",[103,12028,12030],{"className":105,"code":12029,"language":107,"meta":108,"style":108},"\u002F\u002F ❌ ハードコード（危険）\nconst client = new ManagementClient({\n  domain: 'your-tenant.auth0.com',\n  clientId: 'hardcoded-id',\n  clientSecret: 'hardcoded-secret',\n})\n\n\u002F\u002F ✅ 環境変数で管理\nconst client = new ManagementClient({\n  domain: process.env.AUTH0_DOMAIN!,\n  clientId: process.env.AUTH0_CLIENT_ID!,\n  clientSecret: process.env.AUTH0_CLIENT_SECRET!,\n})\n",[90,12031,12032,12036,12050,12060,12070,12080,12084,12088,12092,12106,12117,12128,12139],{"__ignoreMap":108},[112,12033,12034],{"class":114,"line":115},[112,12035,9329],{"class":118},[112,12037,12038,12040,12042,12044,12046,12048],{"class":114,"line":122},[112,12039,153],{"class":125},[112,12041,10100],{"class":156},[112,12043,160],{"class":125},[112,12045,232],{"class":125},[112,12047,10158],{"class":163},[112,12049,167],{"class":129},[112,12051,12052,12055,12058],{"class":114,"line":143},[112,12053,12054],{"class":129},"  domain: ",[112,12056,12057],{"class":136},"'your-tenant.auth0.com'",[112,12059,179],{"class":129},[112,12061,12062,12065,12068],{"class":114,"line":150},[112,12063,12064],{"class":129},"  clientId: ",[112,12066,12067],{"class":136},"'hardcoded-id'",[112,12069,179],{"class":129},[112,12071,12072,12075,12078],{"class":114,"line":170},[112,12073,12074],{"class":129},"  clientSecret: ",[112,12076,12077],{"class":136},"'hardcoded-secret'",[112,12079,179],{"class":129},[112,12081,12082],{"class":114,"line":182},[112,12083,8436],{"class":129},[112,12085,12086],{"class":114,"line":193},[112,12087,147],{"emptyLinePlaceholder":146},[112,12089,12090],{"class":114,"line":205},[112,12091,9343],{"class":118},[112,12093,12094,12096,12098,12100,12102,12104],{"class":114,"line":241},[112,12095,153],{"class":125},[112,12097,10100],{"class":156},[112,12099,160],{"class":125},[112,12101,232],{"class":125},[112,12103,10158],{"class":163},[112,12105,167],{"class":129},[112,12107,12108,12111,12113,12115],{"class":114,"line":247},[112,12109,12110],{"class":129},"  domain: process.env.",[112,12112,9777],{"class":156},[112,12114,3948],{"class":125},[112,12116,179],{"class":129},[112,12118,12119,12122,12124,12126],{"class":114,"line":351},[112,12120,12121],{"class":129},"  clientId: process.env.",[112,12123,9787],{"class":156},[112,12125,3948],{"class":125},[112,12127,179],{"class":129},[112,12129,12130,12133,12135,12137],{"class":114,"line":361},[112,12131,12132],{"class":129},"  clientSecret: process.env.",[112,12134,9797],{"class":156},[112,12136,3948],{"class":125},[112,12138,179],{"class":129},[112,12140,12141],{"class":114,"line":367},[112,12142,8436],{"class":129},[83,12144,12146],{"id":12145},"_3-エラーハンドリング","3. エラーハンドリング",[103,12148,12150],{"className":105,"code":12149,"language":107,"meta":108,"style":108},"try {\n  await this.auth0Service.createUser(input)\n} catch (error) {\n  if (error.message.includes('already exists')) {\n    \u002F\u002F ユーザーが既に存在する場合の処理\n    throw new ConflictException('User already exists')\n  }\n  throw error\n}\n",[90,12151,12152,12159,12171,12179,12197,12202,12217,12221,12229],{"__ignoreMap":108},[112,12153,12154,12157],{"class":114,"line":115},[112,12155,12156],{"class":125},"try",[112,12158,1294],{"class":129},[112,12160,12161,12163,12165,12167,12169],{"class":114,"line":122},[112,12162,5924],{"class":125},[112,12164,10318],{"class":156},[112,12166,11116],{"class":129},[112,12168,10324],{"class":163},[112,12170,11820],{"class":129},[112,12172,12173,12175,12177],{"class":114,"line":143},[112,12174,10929],{"class":129},[112,12176,3686],{"class":125},[112,12178,3689],{"class":129},[112,12180,12181,12183,12186,12189,12191,12194],{"class":114,"line":150},[112,12182,3367],{"class":125},[112,12184,12185],{"class":129}," (error.message.",[112,12187,12188],{"class":163},"includes",[112,12190,425],{"class":129},[112,12192,12193],{"class":136},"'already exists'",[112,12195,12196],{"class":129},")) {\n",[112,12198,12199],{"class":114,"line":170},[112,12200,12201],{"class":118},"    \u002F\u002F ユーザーが既に存在する場合の処理\n",[112,12203,12204,12206,12208,12211,12213,12215],{"class":114,"line":182},[112,12205,4966],{"class":125},[112,12207,232],{"class":125},[112,12209,12210],{"class":163}," ConflictException",[112,12212,425],{"class":129},[112,12214,10766],{"class":136},[112,12216,431],{"class":129},[112,12218,12219],{"class":114,"line":193},[112,12220,3232],{"class":129},[112,12222,12223,12226],{"class":114,"line":205},[112,12224,12225],{"class":125},"  throw",[112,12227,12228],{"class":129}," error\n",[112,12230,12231],{"class":114,"line":241},[112,12232,1452],{"class":129},[83,12234,12236],{"id":12235},"_4-レート制限","4. レート制限",[15,12238,12239],{},"Auth0 Management APIにはレート制限があります",[103,12241,12243],{"className":105,"code":12242,"language":107,"meta":108,"style":108},"import { Throttle } from '@nestjs\u002Fthrottler'\n\n@Controller('users')\nexport class UsersController {\n  @Throttle(10, 60) \u002F\u002F 60秒間に10リクエストまで\n  @Post()\n  async createUser(@Body() dto: CreateUserDto) {\n    return this.auth0Service.createUser(dto)\n  }\n}\n",[90,12244,12245,12257,12261,12273,12283,12305,12313,12333,12345,12349],{"__ignoreMap":108},[112,12246,12247,12249,12252,12254],{"class":114,"line":115},[112,12248,126],{"class":125},[112,12250,12251],{"class":129}," { Throttle } ",[112,12253,133],{"class":125},[112,12255,12256],{"class":136}," '@nestjs\u002Fthrottler'\n",[112,12258,12259],{"class":114,"line":122},[112,12260,147],{"emptyLinePlaceholder":146},[112,12262,12263,12265,12267,12269,12271],{"class":114,"line":143},[112,12264,9901],{"class":129},[112,12266,10878],{"class":163},[112,12268,425],{"class":129},[112,12270,11037],{"class":136},[112,12272,431],{"class":129},[112,12274,12275,12277,12279,12281],{"class":114,"line":150},[112,12276,288],{"class":125},[112,12278,9931],{"class":125},[112,12280,11048],{"class":163},[112,12282,1294],{"class":129},[112,12284,12285,12287,12290,12292,12295,12297,12300,12302],{"class":114,"line":170},[112,12286,11079],{"class":129},[112,12288,12289],{"class":163},"Throttle",[112,12291,425],{"class":129},[112,12293,12294],{"class":156},"10",[112,12296,447],{"class":129},[112,12298,12299],{"class":156},"60",[112,12301,226],{"class":129},[112,12303,12304],{"class":118},"\u002F\u002F 60秒間に10リクエストまで\n",[112,12306,12307,12309,12311],{"class":114,"line":182},[112,12308,11079],{"class":129},[112,12310,11082],{"class":163},[112,12312,630],{"class":129},[112,12314,12315,12317,12319,12321,12323,12325,12327,12329,12331],{"class":114,"line":193},[112,12316,10270],{"class":125},[112,12318,10273],{"class":163},[112,12320,11093],{"class":129},[112,12322,11096],{"class":163},[112,12324,1392],{"class":129},[112,12326,11101],{"class":222},[112,12328,2243],{"class":125},[112,12330,10957],{"class":163},[112,12332,1969],{"class":129},[112,12334,12335,12337,12339,12341,12343],{"class":114,"line":205},[112,12336,3973],{"class":125},[112,12338,10318],{"class":156},[112,12340,11116],{"class":129},[112,12342,10324],{"class":163},[112,12344,11121],{"class":129},[112,12346,12347],{"class":114,"line":241},[112,12348,3232],{"class":129},[112,12350,12351],{"class":114,"line":247},[112,12352,1452],{"class":129},[11,12354,12355],{"id":12355},"テストの実装",[103,12357,12359],{"className":105,"code":12358,"language":107,"meta":108,"style":108},"import { Test, TestingModule } from '@nestjs\u002Ftesting'\nimport { ConfigService } from '@nestjs\u002Fconfig'\nimport { Auth0Service } from '.\u002Fauth0.service'\n\ndescribe('Auth0Service', () => {\n  let service: Auth0Service\n\n  const mockConfigService = {\n    get: jest.fn((key: string) => {\n      const config = {\n        'auth0.domain': 'test.auth0.com',\n        'auth0.clientId': 'test-client-id',\n        'auth0.clientSecret': 'test-secret',\n      }\n      return config[key]\n    }),\n  }\n\n  beforeEach(async () => {\n    const module: TestingModule = await Test.createTestingModule({\n      providers: [\n        Auth0Service,\n        {\n          provide: ConfigService,\n          useValue: mockConfigService,\n        },\n      ],\n    }).compile()\n\n    service = module.get\u003CAuth0Service>(Auth0Service)\n  })\n\n  it('should be defined', () => {\n    expect(service).toBeDefined()\n  })\n\n  \u002F\u002F 実際のAuth0 APIを呼ばないモックテストを実装\n})\n",[90,12360,12361,12373,12383,12393,12397,12413,12426,12430,12441,12464,12475,12487,12499,12511,12515,12522,12526,12530,12534,12549,12573,12578,12583,12588,12593,12598,12602,12607,12617,12621,12642,12646,12650,12666,12679,12683,12687,12692],{"__ignoreMap":108},[112,12362,12363,12365,12368,12370],{"class":114,"line":115},[112,12364,126],{"class":125},[112,12366,12367],{"class":129}," { Test, TestingModule } ",[112,12369,133],{"class":125},[112,12371,12372],{"class":136}," '@nestjs\u002Ftesting'\n",[112,12374,12375,12377,12379,12381],{"class":114,"line":122},[112,12376,126],{"class":125},[112,12378,9964],{"class":129},[112,12380,133],{"class":125},[112,12382,9880],{"class":136},[112,12384,12385,12387,12389,12391],{"class":114,"line":143},[112,12386,126],{"class":125},[112,12388,9887],{"class":129},[112,12390,133],{"class":125},[112,12392,9892],{"class":136},[112,12394,12395],{"class":114,"line":150},[112,12396,147],{"emptyLinePlaceholder":146},[112,12398,12399,12402,12404,12407,12409,12411],{"class":114,"line":170},[112,12400,12401],{"class":163},"describe",[112,12403,425],{"class":129},[112,12405,12406],{"class":136},"'Auth0Service'",[112,12408,595],{"class":129},[112,12410,229],{"class":125},[112,12412,1294],{"class":129},[112,12414,12415,12418,12421,12423],{"class":114,"line":182},[112,12416,12417],{"class":125},"  let",[112,12419,12420],{"class":129}," service",[112,12422,2243],{"class":125},[112,12424,12425],{"class":163}," Auth0Service\n",[112,12427,12428],{"class":114,"line":193},[112,12429,147],{"emptyLinePlaceholder":146},[112,12431,12432,12434,12437,12439],{"class":114,"line":205},[112,12433,1974],{"class":125},[112,12435,12436],{"class":156}," mockConfigService",[112,12438,160],{"class":125},[112,12440,1294],{"class":129},[112,12442,12443,12446,12449,12451,12454,12456,12458,12460,12462],{"class":114,"line":241},[112,12444,12445],{"class":129},"    get: jest.",[112,12447,12448],{"class":163},"fn",[112,12450,219],{"class":129},[112,12452,12453],{"class":222},"key",[112,12455,2243],{"class":125},[112,12457,10478],{"class":156},[112,12459,226],{"class":129},[112,12461,229],{"class":125},[112,12463,1294],{"class":129},[112,12465,12466,12468,12471,12473],{"class":114,"line":247},[112,12467,3575],{"class":125},[112,12469,12470],{"class":156}," config",[112,12472,160],{"class":125},[112,12474,1294],{"class":129},[112,12476,12477,12480,12482,12485],{"class":114,"line":351},[112,12478,12479],{"class":136},"        'auth0.domain'",[112,12481,567],{"class":129},[112,12483,12484],{"class":136},"'test.auth0.com'",[112,12486,179],{"class":129},[112,12488,12489,12492,12494,12497],{"class":114,"line":361},[112,12490,12491],{"class":136},"        'auth0.clientId'",[112,12493,567],{"class":129},[112,12495,12496],{"class":136},"'test-client-id'",[112,12498,179],{"class":129},[112,12500,12501,12504,12506,12509],{"class":114,"line":367},[112,12502,12503],{"class":136},"        'auth0.clientSecret'",[112,12505,567],{"class":129},[112,12507,12508],{"class":136},"'test-secret'",[112,12510,179],{"class":129},[112,12512,12513],{"class":114,"line":373},[112,12514,3643],{"class":129},[112,12516,12517,12519],{"class":114,"line":379},[112,12518,5009],{"class":125},[112,12520,12521],{"class":129}," config[key]\n",[112,12523,12524],{"class":114,"line":1302},[112,12525,370],{"class":129},[112,12527,12528],{"class":114,"line":1502},[112,12529,3232],{"class":129},[112,12531,12532],{"class":114,"line":1507},[112,12533,147],{"emptyLinePlaceholder":146},[112,12535,12536,12539,12541,12543,12545,12547],{"class":114,"line":1512},[112,12537,12538],{"class":163},"  beforeEach",[112,12540,425],{"class":129},[112,12542,3305],{"class":125},[112,12544,3802],{"class":129},[112,12546,229],{"class":125},[112,12548,1294],{"class":129},[112,12550,12551,12553,12556,12558,12561,12563,12565,12568,12571],{"class":114,"line":1518},[112,12552,3472],{"class":125},[112,12554,12555],{"class":156}," module",[112,12557,2243],{"class":125},[112,12559,12560],{"class":163}," TestingModule",[112,12562,160],{"class":125},[112,12564,419],{"class":125},[112,12566,12567],{"class":129}," Test.",[112,12569,12570],{"class":163},"createTestingModule",[112,12572,167],{"class":129},[112,12574,12575],{"class":114,"line":1524},[112,12576,12577],{"class":129},"      providers: [\n",[112,12579,12580],{"class":114,"line":1530},[112,12581,12582],{"class":129},"        Auth0Service,\n",[112,12584,12585],{"class":114,"line":1536},[112,12586,12587],{"class":129},"        {\n",[112,12589,12590],{"class":114,"line":1542},[112,12591,12592],{"class":129},"          provide: ConfigService,\n",[112,12594,12595],{"class":114,"line":2402},[112,12596,12597],{"class":129},"          useValue: mockConfigService,\n",[112,12599,12600],{"class":114,"line":2407},[112,12601,2348],{"class":129},[112,12603,12604],{"class":114,"line":2413},[112,12605,12606],{"class":129},"      ],\n",[112,12608,12609,12612,12615],{"class":114,"line":2446},[112,12610,12611],{"class":129},"    }).",[112,12613,12614],{"class":163},"compile",[112,12616,630],{"class":129},[112,12618,12619],{"class":114,"line":2451},[112,12620,147],{"emptyLinePlaceholder":146},[112,12622,12623,12626,12628,12630,12632,12634,12636,12639],{"class":114,"line":2464},[112,12624,12625],{"class":129},"    service ",[112,12627,576],{"class":125},[112,12629,12555],{"class":156},[112,12631,2124],{"class":129},[112,12633,3344],{"class":163},[112,12635,6944],{"class":129},[112,12637,12638],{"class":163},"Auth0Service",[112,12640,12641],{"class":129},">(Auth0Service)\n",[112,12643,12644],{"class":114,"line":2481},[112,12645,8004],{"class":129},[112,12647,12648],{"class":114,"line":2486},[112,12649,147],{"emptyLinePlaceholder":146},[112,12651,12652,12655,12657,12660,12662,12664],{"class":114,"line":3229},[112,12653,12654],{"class":163},"  it",[112,12656,425],{"class":129},[112,12658,12659],{"class":136},"'should be defined'",[112,12661,595],{"class":129},[112,12663,229],{"class":125},[112,12665,1294],{"class":129},[112,12667,12668,12671,12674,12677],{"class":114,"line":3235},[112,12669,12670],{"class":163},"    expect",[112,12672,12673],{"class":129},"(service).",[112,12675,12676],{"class":163},"toBeDefined",[112,12678,630],{"class":129},[112,12680,12681],{"class":114,"line":3657},[112,12682,8004],{"class":129},[112,12684,12685],{"class":114,"line":3662},[112,12686,147],{"emptyLinePlaceholder":146},[112,12688,12689],{"class":114,"line":3667},[112,12690,12691],{"class":118},"  \u002F\u002F 実際のAuth0 APIを呼ばないモックテストを実装\n",[112,12693,12694],{"class":114,"line":3680},[112,12695,8436],{"class":129},[11,12697,919],{"id":919},[15,12699,12700],{},"Auth0 Management APIをNestJSで使うには：",[31,12702,12703,12709,12715,12720,12726],{},[34,12704,12705,12708],{},[27,12706,12707],{},"認証情報の管理",": 環境変数で安全に管理",[34,12710,12711,12714],{},[27,12712,12713],{},"エラーハンドリング",": 適切なエラー処理とユーザーフィードバック",[34,12716,12717,12719],{},[27,12718,9529],{},": パスワードポリシーとレート制限を実装",[34,12721,12722,12725],{},[27,12723,12724],{},"バリデーション",": DTOでの入力検証",[34,12727,12728,12731],{},[27,12729,12730],{},"テスト",": モックを使った単体テスト",[15,12733,12734],{},"REST APIとGraphQL両方で実装できるため、プロジェクトに応じて選択できます。",[83,12736,2930],{"id":2930},[31,12738,12739,12746,12753],{},[34,12740,12741],{},[759,12742,12745],{"href":12743,"rel":12744},"https:\u002F\u002Fauth0.com\u002Fdocs\u002Fapi\u002Fmanagement\u002Fv2",[763],"Auth0 Management API",[34,12747,12748],{},[759,12749,12752],{"href":12750,"rel":12751},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fauth0",[763],"auth0 npm package",[34,12754,12755],{},[759,12756,12759],{"href":12757,"rel":12758},"https:\u002F\u002Fdocs.nestjs.com\u002Ftechniques\u002Fconfiguration",[763],"NestJS Configuration",[944,12761,12762],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":108,"searchDepth":122,"depth":122,"links":12764},[12765,12766,12771,12775,12779,12784,12790,12791],{"id":9615,"depth":122,"text":9616},{"id":9641,"depth":122,"text":9641,"children":12767},[12768,12769,12770],{"id":7843,"depth":143,"text":7844},{"id":9672,"depth":143,"text":9673},{"id":9705,"depth":143,"text":9706},{"id":9738,"depth":122,"text":9739,"children":12772},[12773,12774],{"id":9742,"depth":143,"text":9742},{"id":9846,"depth":143,"text":9847},{"id":10873,"depth":122,"text":10874,"children":12776},[12777,12778],{"id":10877,"depth":143,"text":10878},{"id":11348,"depth":143,"text":11349},{"id":11485,"depth":122,"text":11486,"children":12780},[12781,12782,12783],{"id":11489,"depth":143,"text":11490},{"id":11663,"depth":143,"text":11664},{"id":11909,"depth":143,"text":11910},{"id":12004,"depth":122,"text":12004,"children":12785},[12786,12787,12788,12789],{"id":12007,"depth":143,"text":12008},{"id":12025,"depth":143,"text":12026},{"id":12145,"depth":143,"text":12146},{"id":12235,"depth":143,"text":12236},{"id":12355,"depth":122,"text":12355},{"id":919,"depth":122,"text":919,"children":12792},[12793],{"id":2930,"depth":143,"text":2930},"2022-10-12","Auth0のManagement APIとNestJSを使って、プログラムからAuth0にユーザーを登録する方法を解説します。REST APIとGraphQL両方の実装例を紹介します。",{"tags":12797},[12798,12799,12800],"nestjs","auth0","user-management","\u002Fblog\u002Fnestjs-auth0-user-registration",{"title":9610,"description":12795},"blog\u002Fnestjs-auth0-user-registration","S01rhZt87YAVglfYCFgaJIkjL4mxYO1Z5K58QkBxUdE",{"id":12806,"title":12807,"body":12808,"date":14599,"description":14600,"extension":962,"meta":14601,"navigation":146,"path":14604,"seo":14605,"stem":14606,"__hash__":14607},"blog\u002Fblog\u002Fnestjs-hasura-auth0-authentication.md","NestJSとHasuraをAuth0で認証する実装ガイド",{"type":8,"value":12809,"toc":14580},[12810,12812,12815,12822,12825,12845,12849,12852,12855,12871,12875,12878,12881,12889,12892,13069,13073,13076,13078,13121,13128,13132,13141,13145,13164,13168,13789,13792,13979,13983,14231,14234,14237,14240,14248,14433,14437,14444,14551,14553,14556,14574,14577],[11,12811,13],{"id":13},[15,12813,12814],{},"NestJSのAPIエンドポイントとHasuraのGraphQLに対する認証基盤としてAuth0を採用した際の実装方法を解説します。",[15,12816,12817],{},[759,12818,12821],{"href":12819,"rel":12820},"https:\u002F\u002Fauth0.com\u002Fjp\u002Fauthentication",[763],"Auth0公式サイト",[11,12823,12824],{"id":12824},"アーキテクチャ",[31,12826,12827,12833,12839],{},[34,12828,12829,12832],{},[27,12830,12831],{},"Hasura"," GraphQLエンドポイントを提供。シンプルなCRUD操作を担当",[34,12834,12835,12838],{},[27,12836,12837],{},"NestJS"," Hasuraで吸収できない複雑なビジネスロジックを担当",[34,12840,12841,12844],{},[27,12842,12843],{},"Auth0"," 両方のエンドポイントを統一的に認証",[11,12846,12848],{"id":12847},"hasuraの認証設定","Hasuraの認証設定",[83,12850,12851],{"id":12851},"基本設定",[15,12853,12854],{},"Hasuraの公式チュートリアルに従って設定を進めます。",[31,12856,12857,12864],{},[34,12858,12859],{},[759,12860,12863],{"href":12861,"rel":12862},"https:\u002F\u002Fhasura.io\u002Flearn\u002Fja\u002Fgraphql\u002Fhasura\u002Fintroduction\u002F",[763],"Hasura認証チュートリアル",[34,12865,12866],{},[759,12867,12870],{"href":12868,"rel":12869},"https:\u002F\u002Fhub.docker.com\u002Fr\u002Fhasura\u002Fgraphql-engine",[763],"Hasura Docker Hub",[83,12872,12874],{"id":12873},"auth0公開鍵の設定","Auth0公開鍵の設定",[15,12876,12877],{},"Docker環境の場合、Auth0から公開鍵を取得し、環境変数に設定してください。",[8992,12879,12880],{"id":12880},"公開鍵の取得",[15,12882,12883,12888],{},[759,12884,12887],{"href":12885,"rel":12886},"https:\u002F\u002Fhasura.io\u002Flearn\u002Fja\u002Fgraphql\u002Fhasura\u002Fauthentication\u002F3-setup-env-vars-hasura\u002F",[763],"公開鍵取得手順","に従って公開鍵を発行します。",[8992,12890,12891],{"id":12891},"docker-compose設定",[103,12893,12895],{"className":9132,"code":12894,"language":9134,"meta":108,"style":108},"# docker-compose.yml\nversion: '3.6'\nservices:\n  postgres:\n    image: postgres\n    restart: always\n    volumes:\n      - db_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n\n  graphql-engine:\n    image: hasura\u002Fgraphql-engine:v1.0.0-beta.6\n    ports:\n      - \"8080:8080\"\n    depends_on:\n      - \"postgres\"\n    restart: always\n    environment:\n      HASURA_GRAPHQL_DATABASE_URL: postgres:\u002F\u002Fpostgres:@postgres:5432\u002Fpostgres\n      HASURA_GRAPHQL_ENABLE_CONSOLE: \"true\"\n      # HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey\n      HASURA_GRAPHQL_JWT_SECRET: '取得した公開鍵を設定'\n\nvolumes:\n  db_data:\n",[90,12896,12897,12902,12911,12917,12923,12932,12942,12948,12955,12959,12966,12975,12982,12989,12996,13003,13011,13017,13027,13037,13042,13052,13056,13062],{"__ignoreMap":108},[112,12898,12899],{"class":114,"line":115},[112,12900,12901],{"class":118},"# docker-compose.yml\n",[112,12903,12904,12906,12908],{"class":114,"line":122},[112,12905,9142],{"class":9141},[112,12907,567],{"class":129},[112,12909,12910],{"class":136},"'3.6'\n",[112,12912,12913,12915],{"class":114,"line":143},[112,12914,9156],{"class":9141},[112,12916,5004],{"class":129},[112,12918,12919,12921],{"class":114,"line":150},[112,12920,9163],{"class":9141},[112,12922,5004],{"class":129},[112,12924,12925,12927,12929],{"class":114,"line":170},[112,12926,9170],{"class":9141},[112,12928,567],{"class":129},[112,12930,12931],{"class":136},"postgres\n",[112,12933,12934,12937,12939],{"class":114,"line":182},[112,12935,12936],{"class":9141},"    restart",[112,12938,567],{"class":129},[112,12940,12941],{"class":136},"always\n",[112,12943,12944,12946],{"class":114,"line":193},[112,12945,9197],{"class":9141},[112,12947,5004],{"class":129},[112,12949,12950,12952],{"class":114,"line":205},[112,12951,9204],{"class":129},[112,12953,12954],{"class":136},"db_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[112,12956,12957],{"class":114,"line":241},[112,12958,147],{"emptyLinePlaceholder":146},[112,12960,12961,12964],{"class":114,"line":247},[112,12962,12963],{"class":9141},"  graphql-engine",[112,12965,5004],{"class":129},[112,12967,12968,12970,12972],{"class":114,"line":351},[112,12969,9170],{"class":9141},[112,12971,567],{"class":129},[112,12973,12974],{"class":136},"hasura\u002Fgraphql-engine:v1.0.0-beta.6\n",[112,12976,12977,12980],{"class":114,"line":361},[112,12978,12979],{"class":9141},"    ports",[112,12981,5004],{"class":129},[112,12983,12984,12986],{"class":114,"line":367},[112,12985,9204],{"class":129},[112,12987,12988],{"class":136},"\"8080:8080\"\n",[112,12990,12991,12994],{"class":114,"line":373},[112,12992,12993],{"class":9141},"    depends_on",[112,12995,5004],{"class":129},[112,12997,12998,13000],{"class":114,"line":379},[112,12999,9204],{"class":129},[112,13001,13002],{"class":136},"\"postgres\"\n",[112,13004,13005,13007,13009],{"class":114,"line":1302},[112,13006,12936],{"class":9141},[112,13008,567],{"class":129},[112,13010,12941],{"class":136},[112,13012,13013,13015],{"class":114,"line":1502},[112,13014,9180],{"class":9141},[112,13016,5004],{"class":129},[112,13018,13019,13022,13024],{"class":114,"line":1507},[112,13020,13021],{"class":9141},"      HASURA_GRAPHQL_DATABASE_URL",[112,13023,567],{"class":129},[112,13025,13026],{"class":136},"postgres:\u002F\u002Fpostgres:@postgres:5432\u002Fpostgres\n",[112,13028,13029,13032,13034],{"class":114,"line":1512},[112,13030,13031],{"class":9141},"      HASURA_GRAPHQL_ENABLE_CONSOLE",[112,13033,567],{"class":129},[112,13035,13036],{"class":136},"\"true\"\n",[112,13038,13039],{"class":114,"line":1518},[112,13040,13041],{"class":118},"      # HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey\n",[112,13043,13044,13047,13049],{"class":114,"line":1524},[112,13045,13046],{"class":9141},"      HASURA_GRAPHQL_JWT_SECRET",[112,13048,567],{"class":129},[112,13050,13051],{"class":136},"'取得した公開鍵を設定'\n",[112,13053,13054],{"class":114,"line":1530},[112,13055,147],{"emptyLinePlaceholder":146},[112,13057,13058,13060],{"class":114,"line":1536},[112,13059,9461],{"class":9141},[112,13061,5004],{"class":129},[112,13063,13064,13067],{"class":114,"line":1542},[112,13065,13066],{"class":9141},"  db_data",[112,13068,5004],{"class":129},[11,13070,13072],{"id":13071},"nestjsの認証実装","NestJSの認証実装",[15,13074,13075],{},"NestJS側のAPIエンドポイントを直接呼び出されないよう、Auth0で保護します。",[83,13077,9742],{"id":9742},[103,13079,13081],{"className":771,"code":13080,"language":773,"meta":108,"style":108},"# .env\n# Auth0 Domain\nAUTH0_ISSUER_URL=\"https:\u002F\u002Fyour-domain.auth0.com\u002F\"\n\n# Auth0 Identifier\nAUTH0_AUDIENCE=\"your-api-identifier\"\n",[90,13082,13083,13087,13092,13102,13106,13111],{"__ignoreMap":108},[112,13084,13085],{"class":114,"line":115},[112,13086,8747],{"class":118},[112,13088,13089],{"class":114,"line":122},[112,13090,13091],{"class":118},"# Auth0 Domain\n",[112,13093,13094,13097,13099],{"class":114,"line":143},[112,13095,13096],{"class":129},"AUTH0_ISSUER_URL",[112,13098,576],{"class":125},[112,13100,13101],{"class":136},"\"https:\u002F\u002Fyour-domain.auth0.com\u002F\"\n",[112,13103,13104],{"class":114,"line":150},[112,13105,147],{"emptyLinePlaceholder":146},[112,13107,13108],{"class":114,"line":170},[112,13109,13110],{"class":118},"# Auth0 Identifier\n",[112,13112,13113,13116,13118],{"class":114,"line":182},[112,13114,13115],{"class":129},"AUTH0_AUDIENCE",[112,13117,576],{"class":125},[112,13119,13120],{"class":136},"\"your-api-identifier\"\n",[15,13122,13123,13124,13127],{},"本番環境では、",[90,13125,13126],{},".env","ファイルではなく、Cloud Run Secretsなどのシークレット管理サービスを使用することを推奨します。",[83,13129,13131],{"id":13130},"authguardの実装","AuthGuardの実装",[15,13133,13134,13135,13140],{},"NestJSの",[759,13136,13139],{"href":13137,"rel":13138},"https:\u002F\u002Fdocs.nestjs.com\u002Fguards",[763],"Guard","機能を使って、リクエストの認証を検証します。",[8992,13142,13144],{"id":13143},"guardの作成","Guardの作成",[103,13146,13148],{"className":771,"code":13147,"language":773,"meta":108,"style":108},"nest g guard auth\u002Fauth-guard\n",[90,13149,13150],{"__ignoreMap":108},[112,13151,13152,13155,13158,13161],{"class":114,"line":115},[112,13153,13154],{"class":163},"nest",[112,13156,13157],{"class":136}," g",[112,13159,13160],{"class":136}," guard",[112,13162,13163],{"class":136}," auth\u002Fauth-guard\n",[8992,13165,13167],{"id":13166},"guardの実装","Guardの実装",[103,13169,13171],{"className":105,"code":13170,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fcommon\u002Fguard\u002Fauth\u002Fauth-guard.guard.ts\nimport {\n  CanActivate,\n  ExecutionContext,\n  Injectable,\n  UnauthorizedException,\n} from '@nestjs\u002Fcommon'\nimport { GqlContextType } from '@nestjs\u002Fgraphql'\nimport { InjectPinoLogger, PinoLogger } from 'nestjs-pino'\nimport { Reflector } from '@nestjs\u002Fcore'\nimport { expressjwt, GetVerificationKey } from 'express-jwt'\nimport { expressJwtSecret } from 'jwks-rsa'\nimport { ConfigService } from '@nestjs\u002Fconfig'\nimport { promisify } from 'util'\n\n@Injectable()\nexport class AuthGuard implements CanActivate {\n  private readonly AUTH0_AUDIENCE: string\n  private readonly AUTH0_ISSUER_URL: string\n\n  constructor(\n    @InjectPinoLogger(AuthGuard.name) private readonly logger: PinoLogger,\n    private readonly reflector: Reflector,\n    private readonly configService: ConfigService,\n  ) {\n    this.AUTH0_AUDIENCE = this.configService.get('AUTH0_AUDIENCE')\n    this.AUTH0_ISSUER_URL = this.configService.get('AUTH0_ISSUER_URL')\n  }\n\n  async canActivate(context: ExecutionContext): Promise\u003Cboolean> {\n    \u002F\u002F GraphQLリクエストはスキップ（Hasura経由の場合）\n    if (context.getType\u003CGqlContextType>() === 'graphql') {\n      return true\n    }\n\n    \u002F\u002F Auth0に対してJWT Tokenの検証を実行\n    const checkJwtToken = await promisify(\n      expressjwt({\n        secret: expressJwtSecret({\n          cache: true,\n          rateLimit: true,\n          jwksRequestsPerMinute: 5,\n          jwksUri: `${process.env.AUTH0_ISSUER_URL}.well-known\u002Fjwks.json`,\n        }) as GetVerificationKey,\n        audience: this.AUTH0_AUDIENCE,\n        issuer: this.AUTH0_ISSUER_URL,\n        algorithms: ['RS256'],\n      }),\n    )\n\n    try {\n      await checkJwtToken(\n        context.switchToHttp().getRequest(),\n        context.switchToHttp().getResponse(),\n      )\n      return true\n    } catch (e: unknown) {\n      throw new UnauthorizedException(e)\n    }\n  }\n}\n",[90,13172,13173,13178,13184,13189,13194,13199,13204,13212,13223,13235,13247,13259,13271,13281,13293,13297,13305,13322,13335,13348,13352,13358,13382,13399,13413,13417,13440,13463,13467,13471,13500,13505,13530,13537,13541,13545,13550,13566,13573,13583,13592,13601,13611,13636,13649,13662,13675,13685,13689,13693,13697,13703,13711,13726,13739,13743,13749,13765,13777,13781,13785],{"__ignoreMap":108},[112,13174,13175],{"class":114,"line":115},[112,13176,13177],{"class":118},"\u002F\u002F src\u002Fcommon\u002Fguard\u002Fauth\u002Fauth-guard.guard.ts\n",[112,13179,13180,13182],{"class":114,"line":122},[112,13181,126],{"class":125},[112,13183,1294],{"class":129},[112,13185,13186],{"class":114,"line":143},[112,13187,13188],{"class":129},"  CanActivate,\n",[112,13190,13191],{"class":114,"line":150},[112,13192,13193],{"class":129},"  ExecutionContext,\n",[112,13195,13196],{"class":114,"line":170},[112,13197,13198],{"class":129},"  Injectable,\n",[112,13200,13201],{"class":114,"line":182},[112,13202,13203],{"class":129},"  UnauthorizedException,\n",[112,13205,13206,13208,13210],{"class":114,"line":193},[112,13207,10929],{"class":129},[112,13209,133],{"class":125},[112,13211,9868],{"class":136},[112,13213,13214,13216,13219,13221],{"class":114,"line":205},[112,13215,126],{"class":125},[112,13217,13218],{"class":129}," { GqlContextType } ",[112,13220,133],{"class":125},[112,13222,11507],{"class":136},[112,13224,13225,13227,13230,13232],{"class":114,"line":241},[112,13226,126],{"class":125},[112,13228,13229],{"class":129}," { InjectPinoLogger, PinoLogger } ",[112,13231,133],{"class":125},[112,13233,13234],{"class":136}," 'nestjs-pino'\n",[112,13236,13237,13239,13242,13244],{"class":114,"line":247},[112,13238,126],{"class":125},[112,13240,13241],{"class":129}," { Reflector } ",[112,13243,133],{"class":125},[112,13245,13246],{"class":136}," '@nestjs\u002Fcore'\n",[112,13248,13249,13251,13254,13256],{"class":114,"line":351},[112,13250,126],{"class":125},[112,13252,13253],{"class":129}," { expressjwt, GetVerificationKey } ",[112,13255,133],{"class":125},[112,13257,13258],{"class":136}," 'express-jwt'\n",[112,13260,13261,13263,13266,13268],{"class":114,"line":361},[112,13262,126],{"class":125},[112,13264,13265],{"class":129}," { expressJwtSecret } ",[112,13267,133],{"class":125},[112,13269,13270],{"class":136}," 'jwks-rsa'\n",[112,13272,13273,13275,13277,13279],{"class":114,"line":367},[112,13274,126],{"class":125},[112,13276,9964],{"class":129},[112,13278,133],{"class":125},[112,13280,9880],{"class":136},[112,13282,13283,13285,13288,13290],{"class":114,"line":373},[112,13284,126],{"class":125},[112,13286,13287],{"class":129}," { promisify } ",[112,13289,133],{"class":125},[112,13291,13292],{"class":136}," 'util'\n",[112,13294,13295],{"class":114,"line":379},[112,13296,147],{"emptyLinePlaceholder":146},[112,13298,13299,13301,13303],{"class":114,"line":1302},[112,13300,9901],{"class":129},[112,13302,10076],{"class":163},[112,13304,630],{"class":129},[112,13306,13307,13309,13311,13314,13317,13320],{"class":114,"line":1502},[112,13308,288],{"class":125},[112,13310,9931],{"class":125},[112,13312,13313],{"class":163}," AuthGuard",[112,13315,13316],{"class":125}," implements",[112,13318,13319],{"class":163}," CanActivate",[112,13321,1294],{"class":129},[112,13323,13324,13326,13328,13331,13333],{"class":114,"line":1507},[112,13325,10094],{"class":125},[112,13327,10097],{"class":125},[112,13329,13330],{"class":222}," AUTH0_AUDIENCE",[112,13332,2243],{"class":125},[112,13334,10006],{"class":156},[112,13336,13337,13339,13341,13344,13346],{"class":114,"line":1512},[112,13338,10094],{"class":125},[112,13340,10097],{"class":125},[112,13342,13343],{"class":222}," AUTH0_ISSUER_URL",[112,13345,2243],{"class":125},[112,13347,10006],{"class":156},[112,13349,13350],{"class":114,"line":1518},[112,13351,147],{"emptyLinePlaceholder":146},[112,13353,13354,13356],{"class":114,"line":1524},[112,13355,10128],{"class":125},[112,13357,2304],{"class":129},[112,13359,13360,13362,13365,13368,13370,13372,13375,13377,13380],{"class":114,"line":1530},[112,13361,11218],{"class":129},[112,13363,13364],{"class":163},"InjectPinoLogger",[112,13366,13367],{"class":129},"(AuthGuard.name) ",[112,13369,10133],{"class":125},[112,13371,10097],{"class":125},[112,13373,13374],{"class":222}," logger",[112,13376,2243],{"class":125},[112,13378,13379],{"class":163}," PinoLogger",[112,13381,179],{"class":129},[112,13383,13384,13387,13389,13392,13394,13397],{"class":114,"line":1536},[112,13385,13386],{"class":125},"    private",[112,13388,10097],{"class":125},[112,13390,13391],{"class":222}," reflector",[112,13393,2243],{"class":125},[112,13395,13396],{"class":163}," Reflector",[112,13398,179],{"class":129},[112,13400,13401,13403,13405,13407,13409,13411],{"class":114,"line":1542},[112,13402,13386],{"class":125},[112,13404,10097],{"class":125},[112,13406,10136],{"class":222},[112,13408,2243],{"class":125},[112,13410,10141],{"class":163},[112,13412,179],{"class":129},[112,13414,13415],{"class":114,"line":2402},[112,13416,11255],{"class":129},[112,13418,13419,13421,13423,13425,13427,13429,13431,13433,13435,13438],{"class":114,"line":2407},[112,13420,10148],{"class":156},[112,13422,2124],{"class":129},[112,13424,13115],{"class":156},[112,13426,160],{"class":125},[112,13428,10318],{"class":156},[112,13430,10171],{"class":129},[112,13432,3344],{"class":163},[112,13434,425],{"class":129},[112,13436,13437],{"class":136},"'AUTH0_AUDIENCE'",[112,13439,431],{"class":129},[112,13441,13442,13444,13446,13448,13450,13452,13454,13456,13458,13461],{"class":114,"line":2413},[112,13443,10148],{"class":156},[112,13445,2124],{"class":129},[112,13447,13096],{"class":156},[112,13449,160],{"class":125},[112,13451,10318],{"class":156},[112,13453,10171],{"class":129},[112,13455,3344],{"class":163},[112,13457,425],{"class":129},[112,13459,13460],{"class":136},"'AUTH0_ISSUER_URL'",[112,13462,431],{"class":129},[112,13464,13465],{"class":114,"line":2446},[112,13466,3232],{"class":129},[112,13468,13469],{"class":114,"line":2451},[112,13470,147],{"emptyLinePlaceholder":146},[112,13472,13473,13475,13478,13480,13482,13484,13487,13489,13491,13493,13495,13498],{"class":114,"line":2464},[112,13474,10270],{"class":125},[112,13476,13477],{"class":163}," canActivate",[112,13479,425],{"class":129},[112,13481,1966],{"class":222},[112,13483,2243],{"class":125},[112,13485,13486],{"class":163}," ExecutionContext",[112,13488,10186],{"class":129},[112,13490,2243],{"class":125},[112,13492,10289],{"class":163},[112,13494,6944],{"class":129},[112,13496,13497],{"class":156},"boolean",[112,13499,10297],{"class":129},[112,13501,13502],{"class":114,"line":2481},[112,13503,13504],{"class":118},"    \u002F\u002F GraphQLリクエストはスキップ（Hasura経由の場合）\n",[112,13506,13507,13509,13512,13515,13517,13520,13523,13525,13528],{"class":114,"line":2486},[112,13508,3511],{"class":125},[112,13510,13511],{"class":129}," (context.",[112,13513,13514],{"class":163},"getType",[112,13516,6944],{"class":129},[112,13518,13519],{"class":163},"GqlContextType",[112,13521,13522],{"class":129},">() ",[112,13524,3624],{"class":125},[112,13526,13527],{"class":136}," 'graphql'",[112,13529,1969],{"class":129},[112,13531,13532,13534],{"class":114,"line":3229},[112,13533,5009],{"class":125},[112,13535,13536],{"class":156}," true\n",[112,13538,13539],{"class":114,"line":3235},[112,13540,3118],{"class":129},[112,13542,13543],{"class":114,"line":3657},[112,13544,147],{"emptyLinePlaceholder":146},[112,13546,13547],{"class":114,"line":3662},[112,13548,13549],{"class":118},"    \u002F\u002F Auth0に対してJWT Tokenの検証を実行\n",[112,13551,13552,13554,13557,13559,13561,13564],{"class":114,"line":3667},[112,13553,3472],{"class":125},[112,13555,13556],{"class":156}," checkJwtToken",[112,13558,160],{"class":125},[112,13560,419],{"class":125},[112,13562,13563],{"class":163}," promisify",[112,13565,2304],{"class":129},[112,13567,13568,13571],{"class":114,"line":3680},[112,13569,13570],{"class":163},"      expressjwt",[112,13572,167],{"class":129},[112,13574,13575,13578,13581],{"class":114,"line":3692},[112,13576,13577],{"class":129},"        secret: ",[112,13579,13580],{"class":163},"expressJwtSecret",[112,13582,167],{"class":129},[112,13584,13585,13588,13590],{"class":114,"line":3716},[112,13586,13587],{"class":129},"          cache: ",[112,13589,4345],{"class":156},[112,13591,179],{"class":129},[112,13593,13594,13597,13599],{"class":114,"line":3737},[112,13595,13596],{"class":129},"          rateLimit: ",[112,13598,4345],{"class":156},[112,13600,179],{"class":129},[112,13602,13603,13606,13609],{"class":114,"line":3742},[112,13604,13605],{"class":129},"          jwksRequestsPerMinute: ",[112,13607,13608],{"class":156},"5",[112,13610,179],{"class":129},[112,13612,13613,13616,13619,13622,13624,13627,13629,13631,13634],{"class":114,"line":3747},[112,13614,13615],{"class":129},"          jwksUri: ",[112,13617,13618],{"class":136},"`${",[112,13620,13621],{"class":129},"process",[112,13623,2124],{"class":136},[112,13625,13626],{"class":129},"env",[112,13628,2124],{"class":136},[112,13630,13096],{"class":156},[112,13632,13633],{"class":136},"}.well-known\u002Fjwks.json`",[112,13635,179],{"class":129},[112,13637,13638,13641,13644,13647],{"class":114,"line":3752},[112,13639,13640],{"class":129},"        }) ",[112,13642,13643],{"class":125},"as",[112,13645,13646],{"class":163}," GetVerificationKey",[112,13648,179],{"class":129},[112,13650,13651,13654,13656,13658,13660],{"class":114,"line":3758},[112,13652,13653],{"class":129},"        audience: ",[112,13655,10168],{"class":156},[112,13657,2124],{"class":129},[112,13659,13115],{"class":156},[112,13661,179],{"class":129},[112,13663,13664,13667,13669,13671,13673],{"class":114,"line":3778},[112,13665,13666],{"class":129},"        issuer: ",[112,13668,10168],{"class":156},[112,13670,2124],{"class":129},[112,13672,13096],{"class":156},[112,13674,179],{"class":129},[112,13676,13677,13680,13683],{"class":114,"line":3787},[112,13678,13679],{"class":129},"        algorithms: [",[112,13681,13682],{"class":136},"'RS256'",[112,13684,746],{"class":129},[112,13686,13687],{"class":114,"line":3809},[112,13688,364],{"class":129},[112,13690,13691],{"class":114,"line":3827},[112,13692,10862],{"class":129},[112,13694,13695],{"class":114,"line":3834},[112,13696,147],{"emptyLinePlaceholder":146},[112,13698,13699,13701],{"class":114,"line":3848},[112,13700,10302],{"class":125},[112,13702,1294],{"class":129},[112,13704,13705,13707,13709],{"class":114,"line":3858},[112,13706,3837],{"class":125},[112,13708,13556],{"class":163},[112,13710,2304],{"class":129},[112,13712,13713,13716,13719,13721,13724],{"class":114,"line":3863},[112,13714,13715],{"class":129},"        context.",[112,13717,13718],{"class":163},"switchToHttp",[112,13720,213],{"class":129},[112,13722,13723],{"class":163},"getRequest",[112,13725,202],{"class":129},[112,13727,13728,13730,13732,13734,13737],{"class":114,"line":3874},[112,13729,13715],{"class":129},[112,13731,13718],{"class":163},[112,13733,213],{"class":129},[112,13735,13736],{"class":163},"getResponse",[112,13738,202],{"class":129},[112,13740,13741],{"class":114,"line":3879},[112,13742,10820],{"class":129},[112,13744,13745,13747],{"class":114,"line":3884},[112,13746,5009],{"class":125},[112,13748,13536],{"class":156},[112,13750,13751,13753,13755,13757,13759,13761,13763],{"class":114,"line":3907},[112,13752,10433],{"class":129},[112,13754,3686],{"class":125},[112,13756,3945],{"class":129},[112,13758,6950],{"class":222},[112,13760,2243],{"class":125},[112,13762,9393],{"class":156},[112,13764,1969],{"class":129},[112,13766,13767,13769,13771,13774],{"class":114,"line":3922},[112,13768,3519],{"class":125},[112,13770,232],{"class":125},[112,13772,13773],{"class":163}," UnauthorizedException",[112,13775,13776],{"class":129},"(e)\n",[112,13778,13779],{"class":114,"line":3935},[112,13780,3118],{"class":129},[112,13782,13783],{"class":114,"line":3940},[112,13784,3232],{"class":129},[112,13786,13787],{"class":114,"line":3954},[112,13788,1452],{"class":129},[83,13790,13791],{"id":13791},"エンドポイントへの適用",[103,13793,13795],{"className":105,"code":13794,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fapp.controller.ts\nimport { Controller, Get, UseGuards } from '@nestjs\u002Fcommon'\nimport { AppService } from '.\u002Fapp.service'\nimport { AuthGuard } from '.\u002Fcommon\u002Fguard\u002Fauth\u002Fauth-guard.guard'\n\n@Controller()\nexport class AppController {\n  constructor(private readonly appService: AppService) {}\n\n  @Get()\n  getHello(): string {\n    return this.appService.getHello()\n  }\n\n  @UseGuards(AuthGuard)\n  @Get('\u002Fprivate')\n  async private() {\n    return { message: '認証成功' }\n  }\n}\n",[90,13796,13797,13802,13813,13825,13837,13841,13849,13860,13880,13884,13892,13906,13920,13924,13928,13938,13951,13960,13971,13975],{"__ignoreMap":108},[112,13798,13799],{"class":114,"line":115},[112,13800,13801],{"class":118},"\u002F\u002F src\u002Fapp.controller.ts\n",[112,13803,13804,13806,13809,13811],{"class":114,"line":122},[112,13805,126],{"class":125},[112,13807,13808],{"class":129}," { Controller, Get, UseGuards } ",[112,13810,133],{"class":125},[112,13812,9868],{"class":136},[112,13814,13815,13817,13820,13822],{"class":114,"line":143},[112,13816,126],{"class":125},[112,13818,13819],{"class":129}," { AppService } ",[112,13821,133],{"class":125},[112,13823,13824],{"class":136}," '.\u002Fapp.service'\n",[112,13826,13827,13829,13832,13834],{"class":114,"line":150},[112,13828,126],{"class":125},[112,13830,13831],{"class":129}," { AuthGuard } ",[112,13833,133],{"class":125},[112,13835,13836],{"class":136}," '.\u002Fcommon\u002Fguard\u002Fauth\u002Fauth-guard.guard'\n",[112,13838,13839],{"class":114,"line":170},[112,13840,147],{"emptyLinePlaceholder":146},[112,13842,13843,13845,13847],{"class":114,"line":182},[112,13844,9901],{"class":129},[112,13846,10878],{"class":163},[112,13848,630],{"class":129},[112,13850,13851,13853,13855,13858],{"class":114,"line":193},[112,13852,288],{"class":125},[112,13854,9931],{"class":125},[112,13856,13857],{"class":163}," AppController",[112,13859,1294],{"class":129},[112,13861,13862,13864,13866,13868,13870,13873,13875,13878],{"class":114,"line":205},[112,13863,10128],{"class":125},[112,13865,425],{"class":129},[112,13867,10133],{"class":125},[112,13869,10097],{"class":125},[112,13871,13872],{"class":222}," appService",[112,13874,2243],{"class":125},[112,13876,13877],{"class":163}," AppService",[112,13879,11070],{"class":129},[112,13881,13882],{"class":114,"line":241},[112,13883,147],{"emptyLinePlaceholder":146},[112,13885,13886,13888,13890],{"class":114,"line":247},[112,13887,11079],{"class":129},[112,13889,11136],{"class":163},[112,13891,630],{"class":129},[112,13893,13894,13897,13900,13902,13904],{"class":114,"line":351},[112,13895,13896],{"class":163},"  getHello",[112,13898,13899],{"class":129},"()",[112,13901,2243],{"class":125},[112,13903,10478],{"class":156},[112,13905,1294],{"class":129},[112,13907,13908,13910,13912,13915,13918],{"class":114,"line":361},[112,13909,3973],{"class":125},[112,13911,10318],{"class":156},[112,13913,13914],{"class":129},".appService.",[112,13916,13917],{"class":163},"getHello",[112,13919,630],{"class":129},[112,13921,13922],{"class":114,"line":367},[112,13923,3232],{"class":129},[112,13925,13926],{"class":114,"line":373},[112,13927,147],{"emptyLinePlaceholder":146},[112,13929,13930,13932,13935],{"class":114,"line":379},[112,13931,11079],{"class":129},[112,13933,13934],{"class":163},"UseGuards",[112,13936,13937],{"class":129},"(AuthGuard)\n",[112,13939,13940,13942,13944,13946,13949],{"class":114,"line":1302},[112,13941,11079],{"class":129},[112,13943,11136],{"class":163},[112,13945,425],{"class":129},[112,13947,13948],{"class":136},"'\u002Fprivate'",[112,13950,431],{"class":129},[112,13952,13953,13955,13958],{"class":114,"line":1502},[112,13954,10270],{"class":125},[112,13956,13957],{"class":163}," private",[112,13959,3313],{"class":129},[112,13961,13962,13964,13966,13969],{"class":114,"line":1507},[112,13963,3973],{"class":125},[112,13965,11332],{"class":129},[112,13967,13968],{"class":136},"'認証成功'",[112,13970,2395],{"class":129},[112,13972,13973],{"class":114,"line":1512},[112,13974,3232],{"class":129},[112,13976,13977],{"class":114,"line":1518},[112,13978,1452],{"class":129},[83,13980,13982],{"id":13981},"cors設定","CORS設定",[103,13984,13986],{"className":105,"code":13985,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fmain.ts\nimport { HttpAdapterHost, NestFactory } from '@nestjs\u002Fcore'\nimport { AppModule } from '.\u002Fapp.module'\nimport { Logger } from 'nestjs-pino'\nimport { AllExceptionsFilter } from '.\u002Fcommon\u002Ffilter\u002Fall-exceptions.filter'\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule, { bufferLogs: true })\n  app.useLogger(app.get(Logger))\n\n  const adapterHost = app.get(HttpAdapterHost)\n  const httpAdapter = adapterHost.httpAdapter\n  const instance = httpAdapter.getInstance()\n  app.useGlobalFilters(new AllExceptionsFilter(instance))\n\n  app.enableCors({\n    origin: '*',\n    allowedHeaders:\n      'Origin, X-Requested-With, Content-Type, Accept, Authorization',\n  })\n\n  await app.listen(3000)\n}\nbootstrap()\n",[90,13987,13988,13993,14004,14016,14027,14039,14043,14054,14078,14094,14098,14115,14127,14144,14162,14166,14175,14184,14189,14196,14200,14204,14220,14224],{"__ignoreMap":108},[112,13989,13990],{"class":114,"line":115},[112,13991,13992],{"class":118},"\u002F\u002F src\u002Fmain.ts\n",[112,13994,13995,13997,14000,14002],{"class":114,"line":122},[112,13996,126],{"class":125},[112,13998,13999],{"class":129}," { HttpAdapterHost, NestFactory } ",[112,14001,133],{"class":125},[112,14003,13246],{"class":136},[112,14005,14006,14008,14011,14013],{"class":114,"line":143},[112,14007,126],{"class":125},[112,14009,14010],{"class":129}," { AppModule } ",[112,14012,133],{"class":125},[112,14014,14015],{"class":136}," '.\u002Fapp.module'\n",[112,14017,14018,14020,14023,14025],{"class":114,"line":150},[112,14019,126],{"class":125},[112,14021,14022],{"class":129}," { Logger } ",[112,14024,133],{"class":125},[112,14026,13234],{"class":136},[112,14028,14029,14031,14034,14036],{"class":114,"line":170},[112,14030,126],{"class":125},[112,14032,14033],{"class":129}," { AllExceptionsFilter } ",[112,14035,133],{"class":125},[112,14037,14038],{"class":136}," '.\u002Fcommon\u002Ffilter\u002Fall-exceptions.filter'\n",[112,14040,14041],{"class":114,"line":182},[112,14042,147],{"emptyLinePlaceholder":146},[112,14044,14045,14047,14049,14052],{"class":114,"line":193},[112,14046,3305],{"class":125},[112,14048,1958],{"class":125},[112,14050,14051],{"class":163}," bootstrap",[112,14053,3313],{"class":129},[112,14055,14056,14058,14061,14063,14065,14068,14071,14074,14076],{"class":114,"line":205},[112,14057,1974],{"class":125},[112,14059,14060],{"class":156}," app",[112,14062,160],{"class":125},[112,14064,419],{"class":125},[112,14066,14067],{"class":129}," NestFactory.",[112,14069,14070],{"class":163},"create",[112,14072,14073],{"class":129},"(AppModule, { bufferLogs: ",[112,14075,4345],{"class":156},[112,14077,11585],{"class":129},[112,14079,14080,14083,14086,14089,14091],{"class":114,"line":241},[112,14081,14082],{"class":129},"  app.",[112,14084,14085],{"class":163},"useLogger",[112,14087,14088],{"class":129},"(app.",[112,14090,3344],{"class":163},[112,14092,14093],{"class":129},"(Logger))\n",[112,14095,14096],{"class":114,"line":247},[112,14097,147],{"emptyLinePlaceholder":146},[112,14099,14100,14102,14105,14107,14110,14112],{"class":114,"line":351},[112,14101,1974],{"class":125},[112,14103,14104],{"class":156}," adapterHost",[112,14106,160],{"class":125},[112,14108,14109],{"class":129}," app.",[112,14111,3344],{"class":163},[112,14113,14114],{"class":129},"(HttpAdapterHost)\n",[112,14116,14117,14119,14122,14124],{"class":114,"line":361},[112,14118,1974],{"class":125},[112,14120,14121],{"class":156}," httpAdapter",[112,14123,160],{"class":125},[112,14125,14126],{"class":129}," adapterHost.httpAdapter\n",[112,14128,14129,14131,14134,14136,14139,14142],{"class":114,"line":367},[112,14130,1974],{"class":125},[112,14132,14133],{"class":156}," instance",[112,14135,160],{"class":125},[112,14137,14138],{"class":129}," httpAdapter.",[112,14140,14141],{"class":163},"getInstance",[112,14143,630],{"class":129},[112,14145,14146,14148,14151,14153,14156,14159],{"class":114,"line":373},[112,14147,14082],{"class":129},[112,14149,14150],{"class":163},"useGlobalFilters",[112,14152,425],{"class":129},[112,14154,14155],{"class":125},"new",[112,14157,14158],{"class":163}," AllExceptionsFilter",[112,14160,14161],{"class":129},"(instance))\n",[112,14163,14164],{"class":114,"line":379},[112,14165,147],{"emptyLinePlaceholder":146},[112,14167,14168,14170,14173],{"class":114,"line":1302},[112,14169,14082],{"class":129},[112,14171,14172],{"class":163},"enableCors",[112,14174,167],{"class":129},[112,14176,14177,14180,14182],{"class":114,"line":1502},[112,14178,14179],{"class":129},"    origin: ",[112,14181,2577],{"class":136},[112,14183,179],{"class":129},[112,14185,14186],{"class":114,"line":1507},[112,14187,14188],{"class":129},"    allowedHeaders:\n",[112,14190,14191,14194],{"class":114,"line":1512},[112,14192,14193],{"class":136},"      'Origin, X-Requested-With, Content-Type, Accept, Authorization'",[112,14195,179],{"class":129},[112,14197,14198],{"class":114,"line":1518},[112,14199,8004],{"class":129},[112,14201,14202],{"class":114,"line":1524},[112,14203,147],{"emptyLinePlaceholder":146},[112,14205,14206,14208,14210,14213,14215,14218],{"class":114,"line":1530},[112,14207,5924],{"class":125},[112,14209,14109],{"class":129},[112,14211,14212],{"class":163},"listen",[112,14214,425],{"class":129},[112,14216,14217],{"class":156},"3000",[112,14219,431],{"class":129},[112,14221,14222],{"class":114,"line":1536},[112,14223,1452],{"class":129},[112,14225,14226,14229],{"class":114,"line":1542},[112,14227,14228],{"class":163},"bootstrap",[112,14230,630],{"class":129},[11,14232,14233],{"id":14233},"動作確認",[83,14235,14236],{"id":14236},"トークン取得スクリプト",[15,14238,14239],{},"Auth0からアクセストークンを取得します。",[15,14241,14242,14243],{},"参考: ",[759,14244,14247],{"href":14245,"rel":14246},"https:\u002F\u002Fdev.classmethod.jp\u002Farticles\u002Fauth0-nestjs-backend-sample\u002F",[763],"Auth0 + NestJS バックエンドサンプル",[103,14249,14251],{"className":771,"code":14250,"language":773,"meta":108,"style":108},"#!\u002Fusr\u002Fbin\u002Fenv bash\n\nauth_url=https:\u002F\u002Fyour-domain.auth0.com\nclient_id=your_client_id\nclient_secret=your_client_secret\nusername=\"user@example.com\"\npassword=\"password\"\n\necho \"🎁 id_tokenを取得中...\"\n\ncurl -s --request POST \\\n  --url ${auth_url}\u002Foauth\u002Ftoken \\\n  --header 'content-type: application\u002Fx-www-form-urlencoded' \\\n  --data grant_type=password \\\n  --data username=${username} \\\n  --data password=${password} \\\n  --data client_id=${client_id} \\\n  --data client_secret=${client_secret}\n\necho \"\\n\"\n",[90,14252,14253,14258,14262,14272,14281,14290,14300,14310,14314,14322,14326,14342,14355,14365,14375,14388,14400,14412,14422,14426],{"__ignoreMap":108},[112,14254,14255],{"class":114,"line":115},[112,14256,14257],{"class":118},"#!\u002Fusr\u002Fbin\u002Fenv bash\n",[112,14259,14260],{"class":114,"line":122},[112,14261,147],{"emptyLinePlaceholder":146},[112,14263,14264,14267,14269],{"class":114,"line":143},[112,14265,14266],{"class":129},"auth_url",[112,14268,576],{"class":125},[112,14270,14271],{"class":136},"https:\u002F\u002Fyour-domain.auth0.com\n",[112,14273,14274,14277,14279],{"class":114,"line":150},[112,14275,14276],{"class":129},"client_id",[112,14278,576],{"class":125},[112,14280,9834],{"class":136},[112,14282,14283,14286,14288],{"class":114,"line":170},[112,14284,14285],{"class":129},"client_secret",[112,14287,576],{"class":125},[112,14289,9843],{"class":136},[112,14291,14292,14295,14297],{"class":114,"line":182},[112,14293,14294],{"class":129},"username",[112,14296,576],{"class":125},[112,14298,14299],{"class":136},"\"user@example.com\"\n",[112,14301,14302,14305,14307],{"class":114,"line":193},[112,14303,14304],{"class":129},"password",[112,14306,576],{"class":125},[112,14308,14309],{"class":136},"\"password\"\n",[112,14311,14312],{"class":114,"line":205},[112,14313,147],{"emptyLinePlaceholder":146},[112,14315,14316,14319],{"class":114,"line":241},[112,14317,14318],{"class":156},"echo",[112,14320,14321],{"class":136}," \"🎁 id_tokenを取得中...\"\n",[112,14323,14324],{"class":114,"line":247},[112,14325,147],{"emptyLinePlaceholder":146},[112,14327,14328,14331,14334,14337,14340],{"class":114,"line":351},[112,14329,14330],{"class":163},"curl",[112,14332,14333],{"class":156}," -s",[112,14335,14336],{"class":156}," --request",[112,14338,14339],{"class":136}," POST",[112,14341,8535],{"class":156},[112,14343,14344,14347,14350,14353],{"class":114,"line":361},[112,14345,14346],{"class":156},"  --url",[112,14348,14349],{"class":129}," ${auth_url}",[112,14351,14352],{"class":136},"\u002Foauth\u002Ftoken",[112,14354,8535],{"class":156},[112,14356,14357,14360,14363],{"class":114,"line":367},[112,14358,14359],{"class":156},"  --header",[112,14361,14362],{"class":136}," 'content-type: application\u002Fx-www-form-urlencoded'",[112,14364,8535],{"class":156},[112,14366,14367,14370,14373],{"class":114,"line":373},[112,14368,14369],{"class":156},"  --data",[112,14371,14372],{"class":136}," grant_type=password",[112,14374,8535],{"class":156},[112,14376,14377,14379,14382,14385],{"class":114,"line":379},[112,14378,14369],{"class":156},[112,14380,14381],{"class":136}," username=",[112,14383,14384],{"class":129},"${username} ",[112,14386,14387],{"class":156},"\\\n",[112,14389,14390,14392,14395,14398],{"class":114,"line":1302},[112,14391,14369],{"class":156},[112,14393,14394],{"class":136}," password=",[112,14396,14397],{"class":129},"${password} ",[112,14399,14387],{"class":156},[112,14401,14402,14404,14407,14410],{"class":114,"line":1502},[112,14403,14369],{"class":156},[112,14405,14406],{"class":136}," client_id=",[112,14408,14409],{"class":129},"${client_id} ",[112,14411,14387],{"class":156},[112,14413,14414,14416,14419],{"class":114,"line":1507},[112,14415,14369],{"class":156},[112,14417,14418],{"class":136}," client_secret=",[112,14420,14421],{"class":129},"${client_secret}\n",[112,14423,14424],{"class":114,"line":1512},[112,14425,147],{"emptyLinePlaceholder":146},[112,14427,14428,14430],{"class":114,"line":1518},[112,14429,14318],{"class":156},[112,14431,14432],{"class":136}," \"\\n\"\n",[83,14434,14436],{"id":14435},"apiテスト","APIテスト",[15,14438,14439,14440,14443],{},"取得した",[90,14441,14442],{},"id_token","を使ってAPIをテストします。",[103,14445,14447],{"className":771,"code":14446,"language":773,"meta":108,"style":108},"# 認証成功\ncurl -i -X GET 'http:\u002F\u002Flocalhost:3000\u002Fprivate' \\\n  -H 'Authorization: Bearer \u003Cid_token>'\n\n# レスポンス例\n# HTTP\u002F1.1 200 OK\n# Content-Type: application\u002Fjson; charset=utf-8\n#\n# {\"message\":\"認証成功\"}\n\n# 認証失敗（不正なトークン）\ncurl -i -X GET 'http:\u002F\u002Flocalhost:3000\u002Fprivate' \\\n  -H 'Authorization: Bearer invalid_token'\n\n# レスポンス例\n# HTTP\u002F1.1 401 Unauthorized\n",[90,14448,14449,14454,14471,14479,14483,14488,14493,14498,14503,14508,14512,14517,14531,14538,14542,14546],{"__ignoreMap":108},[112,14450,14451],{"class":114,"line":115},[112,14452,14453],{"class":118},"# 認証成功\n",[112,14455,14456,14458,14461,14464,14466,14469],{"class":114,"line":122},[112,14457,14330],{"class":163},[112,14459,14460],{"class":156}," -i",[112,14462,14463],{"class":156}," -X",[112,14465,1961],{"class":136},[112,14467,14468],{"class":136}," 'http:\u002F\u002Flocalhost:3000\u002Fprivate'",[112,14470,8535],{"class":156},[112,14472,14473,14476],{"class":114,"line":143},[112,14474,14475],{"class":156},"  -H",[112,14477,14478],{"class":136}," 'Authorization: Bearer \u003Cid_token>'\n",[112,14480,14481],{"class":114,"line":150},[112,14482,147],{"emptyLinePlaceholder":146},[112,14484,14485],{"class":114,"line":170},[112,14486,14487],{"class":118},"# レスポンス例\n",[112,14489,14490],{"class":114,"line":182},[112,14491,14492],{"class":118},"# HTTP\u002F1.1 200 OK\n",[112,14494,14495],{"class":114,"line":193},[112,14496,14497],{"class":118},"# Content-Type: application\u002Fjson; charset=utf-8\n",[112,14499,14500],{"class":114,"line":205},[112,14501,14502],{"class":118},"#\n",[112,14504,14505],{"class":114,"line":241},[112,14506,14507],{"class":118},"# {\"message\":\"認証成功\"}\n",[112,14509,14510],{"class":114,"line":247},[112,14511,147],{"emptyLinePlaceholder":146},[112,14513,14514],{"class":114,"line":351},[112,14515,14516],{"class":118},"# 認証失敗（不正なトークン）\n",[112,14518,14519,14521,14523,14525,14527,14529],{"class":114,"line":361},[112,14520,14330],{"class":163},[112,14522,14460],{"class":156},[112,14524,14463],{"class":156},[112,14526,1961],{"class":136},[112,14528,14468],{"class":136},[112,14530,8535],{"class":156},[112,14532,14533,14535],{"class":114,"line":367},[112,14534,14475],{"class":156},[112,14536,14537],{"class":136}," 'Authorization: Bearer invalid_token'\n",[112,14539,14540],{"class":114,"line":373},[112,14541,147],{"emptyLinePlaceholder":146},[112,14543,14544],{"class":114,"line":379},[112,14545,14487],{"class":118},[112,14547,14548],{"class":114,"line":1302},[112,14549,14550],{"class":118},"# HTTP\u002F1.1 401 Unauthorized\n",[11,14552,919],{"id":919},[15,14554,14555],{},"Auth0を使ってNestJSとHasuraの認証を統一することで、以下のメリットが得られます。",[31,14557,14558,14564,14569],{},[34,14559,14560,14563],{},[27,14561,14562],{},"一貫性"," 単一の認証基盤でマイクロサービス全体を保護",[34,14565,14566,14568],{},[27,14567,9529],{}," JWT検証により、改ざんされたトークンを検出",[34,14570,14571,14573],{},[27,14572,1134],{}," Auth0の豊富な機能（MFA、ソーシャルログインなど）を活用",[15,14575,14576],{},"この実装パターンは、マイクロサービスアーキテクチャにおける認証のベストプラクティスの一つです。",[944,14578,14579],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":108,"searchDepth":122,"depth":122,"links":14581},[14582,14583,14584,14588,14594,14598],{"id":13,"depth":122,"text":13},{"id":12824,"depth":122,"text":12824},{"id":12847,"depth":122,"text":12848,"children":14585},[14586,14587],{"id":12851,"depth":143,"text":12851},{"id":12873,"depth":143,"text":12874},{"id":13071,"depth":122,"text":13072,"children":14589},[14590,14591,14592,14593],{"id":9742,"depth":143,"text":9742},{"id":13130,"depth":143,"text":13131},{"id":13791,"depth":143,"text":13791},{"id":13981,"depth":143,"text":13982},{"id":14233,"depth":122,"text":14233,"children":14595},[14596,14597],{"id":14236,"depth":143,"text":14236},{"id":14435,"depth":143,"text":14436},{"id":919,"depth":122,"text":919},"2022-08-20","NestJSのAPIエンドポイントとHasuraのGraphQLをAuth0で統一的に認証する方法を解説します。Guardパターンを使った実装例とJWT検証の実践的なコード例を紹介します。",{"tags":14602},[11915,14603,12798,12799],"hasura","\u002Fblog\u002Fnestjs-hasura-auth0-authentication",{"title":12807,"description":14600},"blog\u002Fnestjs-hasura-auth0-authentication","zOdI1WwHXtW43qiPJSq8rhtIkkQC2i7MR7u_Kr4eeBo",{"id":14609,"title":14610,"body":14611,"date":18862,"description":18863,"extension":962,"meta":18864,"navigation":146,"path":18870,"seo":18871,"stem":18872,"__hash__":18873},"blog\u002Fblog\u002Fnestjs-ci-environment-hands-on.md","NestJS + Jest + GitHub ActionsでCI環境を構築する実践ガイド",{"type":8,"value":14612,"toc":18823},[14613,14615,14618,14622,14625,14632,14635,14652,14654,14665,14668,14705,14708,14712,14801,14804,14821,14825,14828,14832,14917,14921,15006,15010,15125,15129,15199,15203,15207,15368,15371,15450,15454,15457,15491,15494,15667,15671,15980,15984,15987,16015,16019,16451,16455,16623,16626,16694,16698,16715,16947,16951,16976,17397,17400,17404,17542,17546,17563,18224,18227,18266,18269,18283,18287,18291,18344,18347,18597,18601,18766,18768,18771,18776,18790,18795,18817,18820],[11,14614,13],{"id":13},[15,14616,14617],{},"NestJSプロジェクトにJestを使ったE2Eテストを導入し、GitHub Actionsで自動化するまでの手順を解説します。",[11,14619,14621],{"id":14620},"ciとは","CIとは",[15,14623,14624],{},"CI（継続的インテグレーション）は、開発者がコード変更を定期的にリポジトリにマージし、自動化されたビルドとテストを実行するDevOps開発手法です。",[15,14626,14242,14627],{},[759,14628,14631],{"href":14629,"rel":14630},"https:\u002F\u002Faws.amazon.com\u002Fjp\u002Fdevops\u002Fcontinuous-integration\u002F",[763],"AWS - 継続的インテグレーション",[11,14633,14634],{"id":14634},"ハンズオンのゴール",[31,14636,14637,14640,14643,14646,14649],{},[34,14638,14639],{},"NestJSプロジェクトの構築",[34,14641,14642],{},"Docker + MySQLでの開発環境構築",[34,14644,14645],{},"TypeORMを使ったCRUD機能の実装",[34,14647,14648],{},"E2Eテストの実装",[34,14650,14651],{},"GitHub ActionsでのCI自動化",[11,14653,7094],{"id":7094},[31,14655,14656,14659,14662],{},[34,14657,14658],{},"macOS環境（Windows環境では一部動作が異なる可能性あり）",[34,14660,14661],{},"Docker Desktop v3.4.0以上がインストール済み",[34,14663,14664],{},"Node.js v14以上がインストール済み",[11,14666,14667],{"id":14667},"技術スタック",[31,14669,14670,14676,14681,14687,14693,14699],{},[34,14671,14672,14675],{},[27,14673,14674],{},"Node.js"," サーバーサイドJavaScript実行環境",[34,14677,14678,14680],{},[27,14679,12837],{}," スケーラブルなNode.jsフレームワーク",[34,14682,14683,14686],{},[27,14684,14685],{},"TypeORM"," TypeScript用ORマッパー",[34,14688,14689,14692],{},[27,14690,14691],{},"MySQL 8.0"," リレーショナルデータベース",[34,14694,14695,14698],{},[27,14696,14697],{},"Jest"," JavaScriptテストフレームワーク",[34,14700,14701,14704],{},[27,14702,14703],{},"Docker"," コンテナ仮想化プラットフォーム",[11,14706,14707],{"id":14707},"プロジェクトセットアップ",[83,14709,14711],{"id":14710},"nestjs-cliのインストールとプロジェクト作成","NestJS CLIのインストールとプロジェクト作成",[103,14713,14715],{"className":771,"code":14714,"language":773,"meta":108,"style":108},"# グローバルにCLIをインストール\nnpm install -g @nestjs\u002Fcli\n\n# プロジェクトを作成（npmを選択）\nnest new meetup-dev\n\n# プロジェクトディレクトリに移動\ncd meetup-dev\n\n# 依存関係をインストール\nnpm install\n\n# 開発サーバーを起動\nnpm run start:dev\n",[90,14716,14717,14722,14734,14738,14743,14752,14756,14761,14767,14771,14776,14783,14787,14792],{"__ignoreMap":108},[112,14718,14719],{"class":114,"line":115},[112,14720,14721],{"class":118},"# グローバルにCLIをインストール\n",[112,14723,14724,14726,14728,14731],{"class":114,"line":122},[112,14725,6832],{"class":163},[112,14727,7883],{"class":136},[112,14729,14730],{"class":156}," -g",[112,14732,14733],{"class":136}," @nestjs\u002Fcli\n",[112,14735,14736],{"class":114,"line":143},[112,14737,147],{"emptyLinePlaceholder":146},[112,14739,14740],{"class":114,"line":150},[112,14741,14742],{"class":118},"# プロジェクトを作成（npmを選択）\n",[112,14744,14745,14747,14749],{"class":114,"line":170},[112,14746,13154],{"class":163},[112,14748,232],{"class":136},[112,14750,14751],{"class":136}," meetup-dev\n",[112,14753,14754],{"class":114,"line":182},[112,14755,147],{"emptyLinePlaceholder":146},[112,14757,14758],{"class":114,"line":193},[112,14759,14760],{"class":118},"# プロジェクトディレクトリに移動\n",[112,14762,14763,14765],{"class":114,"line":205},[112,14764,6891],{"class":156},[112,14766,14751],{"class":136},[112,14768,14769],{"class":114,"line":241},[112,14770,147],{"emptyLinePlaceholder":146},[112,14772,14773],{"class":114,"line":247},[112,14774,14775],{"class":118},"# 依存関係をインストール\n",[112,14777,14778,14780],{"class":114,"line":351},[112,14779,6832],{"class":163},[112,14781,14782],{"class":136}," install\n",[112,14784,14785],{"class":114,"line":361},[112,14786,147],{"emptyLinePlaceholder":146},[112,14788,14789],{"class":114,"line":367},[112,14790,14791],{"class":118},"# 開発サーバーを起動\n",[112,14793,14794,14796,14798],{"class":114,"line":373},[112,14795,6832],{"class":163},[112,14797,6901],{"class":136},[112,14799,14800],{"class":136}," start:dev\n",[15,14802,14803],{},"動作確認：",[103,14805,14807],{"className":771,"code":14806,"language":773,"meta":108,"style":108},"curl http:\u002F\u002Flocalhost:3000\n# \"Hello World!\" が返ればOK\n",[90,14808,14809,14816],{"__ignoreMap":108},[112,14810,14811,14813],{"class":114,"line":115},[112,14812,14330],{"class":163},[112,14814,14815],{"class":136}," http:\u002F\u002Flocalhost:3000\n",[112,14817,14818],{"class":114,"line":122},[112,14819,14820],{"class":118},"# \"Hello World!\" が返ればOK\n",[83,14822,14824],{"id":14823},"nestjsの基本構造","NestJSの基本構造",[15,14826,14827],{},"NestJSは以下のコンポーネントで構成されます",[8992,14829,14831],{"id":14830},"maints-エントリーポイント","main.ts - エントリーポイント",[103,14833,14835],{"className":105,"code":14834,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fmain.ts\nimport { NestFactory } from '@nestjs\u002Fcore'\nimport { AppModule } from '.\u002Fapp.module'\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule)\n  await app.listen(3000)\n}\nbootstrap()\n",[90,14836,14837,14841,14852,14862,14866,14876,14893,14907,14911],{"__ignoreMap":108},[112,14838,14839],{"class":114,"line":115},[112,14840,13992],{"class":118},[112,14842,14843,14845,14848,14850],{"class":114,"line":122},[112,14844,126],{"class":125},[112,14846,14847],{"class":129}," { NestFactory } ",[112,14849,133],{"class":125},[112,14851,13246],{"class":136},[112,14853,14854,14856,14858,14860],{"class":114,"line":143},[112,14855,126],{"class":125},[112,14857,14010],{"class":129},[112,14859,133],{"class":125},[112,14861,14015],{"class":136},[112,14863,14864],{"class":114,"line":150},[112,14865,147],{"emptyLinePlaceholder":146},[112,14867,14868,14870,14872,14874],{"class":114,"line":170},[112,14869,3305],{"class":125},[112,14871,1958],{"class":125},[112,14873,14051],{"class":163},[112,14875,3313],{"class":129},[112,14877,14878,14880,14882,14884,14886,14888,14890],{"class":114,"line":182},[112,14879,1974],{"class":125},[112,14881,14060],{"class":156},[112,14883,160],{"class":125},[112,14885,419],{"class":125},[112,14887,14067],{"class":129},[112,14889,14070],{"class":163},[112,14891,14892],{"class":129},"(AppModule)\n",[112,14894,14895,14897,14899,14901,14903,14905],{"class":114,"line":193},[112,14896,5924],{"class":125},[112,14898,14109],{"class":129},[112,14900,14212],{"class":163},[112,14902,425],{"class":129},[112,14904,14217],{"class":156},[112,14906,431],{"class":129},[112,14908,14909],{"class":114,"line":205},[112,14910,1452],{"class":129},[112,14912,14913,14915],{"class":114,"line":241},[112,14914,14228],{"class":163},[112,14916,630],{"class":129},[8992,14918,14920],{"id":14919},"module-依存関係の管理","Module - 依存関係の管理",[103,14922,14924],{"className":105,"code":14923,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fmodules\u002Fapp.module.ts\nimport { Module } from '@nestjs\u002Fcommon'\nimport { AppController } from '..\u002Fcontrollers\u002Fapp.controller'\nimport { AppService } from '..\u002Fservices\u002Fapp.service'\n\n@Module({\n  imports: [],\n  controllers: [AppController],\n  providers: [AppService],\n})\nexport class AppModule {}\n",[90,14925,14926,14931,14941,14953,14964,14968,14976,14981,14986,14991,14995],{"__ignoreMap":108},[112,14927,14928],{"class":114,"line":115},[112,14929,14930],{"class":118},"\u002F\u002F src\u002Fmodules\u002Fapp.module.ts\n",[112,14932,14933,14935,14937,14939],{"class":114,"line":122},[112,14934,126],{"class":125},[112,14936,9863],{"class":129},[112,14938,133],{"class":125},[112,14940,9868],{"class":136},[112,14942,14943,14945,14948,14950],{"class":114,"line":143},[112,14944,126],{"class":125},[112,14946,14947],{"class":129}," { AppController } ",[112,14949,133],{"class":125},[112,14951,14952],{"class":136}," '..\u002Fcontrollers\u002Fapp.controller'\n",[112,14954,14955,14957,14959,14961],{"class":114,"line":150},[112,14956,126],{"class":125},[112,14958,13819],{"class":129},[112,14960,133],{"class":125},[112,14962,14963],{"class":136}," '..\u002Fservices\u002Fapp.service'\n",[112,14965,14966],{"class":114,"line":170},[112,14967,147],{"emptyLinePlaceholder":146},[112,14969,14970,14972,14974],{"class":114,"line":182},[112,14971,9901],{"class":129},[112,14973,9851],{"class":163},[112,14975,167],{"class":129},[112,14977,14978],{"class":114,"line":193},[112,14979,14980],{"class":129},"  imports: [],\n",[112,14982,14983],{"class":114,"line":205},[112,14984,14985],{"class":129},"  controllers: [AppController],\n",[112,14987,14988],{"class":114,"line":241},[112,14989,14990],{"class":129},"  providers: [AppService],\n",[112,14992,14993],{"class":114,"line":247},[112,14994,8436],{"class":129},[112,14996,14997,14999,15001,15004],{"class":114,"line":351},[112,14998,288],{"class":125},[112,15000,9931],{"class":125},[112,15002,15003],{"class":163}," AppModule",[112,15005,9937],{"class":129},[8992,15007,15009],{"id":15008},"controller-ルーティング","Controller - ルーティング",[103,15011,15013],{"className":105,"code":15012,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fcontrollers\u002Fapp.controller.ts\nimport { Controller, Get } from '@nestjs\u002Fcommon'\nimport { AppService } from '..\u002Fservices\u002Fapp.service'\n\n@Controller()\nexport class AppController {\n  constructor(private readonly appService: AppService) {}\n\n  @Get()\n  getHello(): string {\n    return this.appService.getHello()\n  }\n}\n",[90,15014,15015,15020,15031,15041,15045,15053,15063,15081,15085,15093,15105,15117,15121],{"__ignoreMap":108},[112,15016,15017],{"class":114,"line":115},[112,15018,15019],{"class":118},"\u002F\u002F src\u002Fcontrollers\u002Fapp.controller.ts\n",[112,15021,15022,15024,15027,15029],{"class":114,"line":122},[112,15023,126],{"class":125},[112,15025,15026],{"class":129}," { Controller, Get } ",[112,15028,133],{"class":125},[112,15030,9868],{"class":136},[112,15032,15033,15035,15037,15039],{"class":114,"line":143},[112,15034,126],{"class":125},[112,15036,13819],{"class":129},[112,15038,133],{"class":125},[112,15040,14963],{"class":136},[112,15042,15043],{"class":114,"line":150},[112,15044,147],{"emptyLinePlaceholder":146},[112,15046,15047,15049,15051],{"class":114,"line":170},[112,15048,9901],{"class":129},[112,15050,10878],{"class":163},[112,15052,630],{"class":129},[112,15054,15055,15057,15059,15061],{"class":114,"line":182},[112,15056,288],{"class":125},[112,15058,9931],{"class":125},[112,15060,13857],{"class":163},[112,15062,1294],{"class":129},[112,15064,15065,15067,15069,15071,15073,15075,15077,15079],{"class":114,"line":193},[112,15066,10128],{"class":125},[112,15068,425],{"class":129},[112,15070,10133],{"class":125},[112,15072,10097],{"class":125},[112,15074,13872],{"class":222},[112,15076,2243],{"class":125},[112,15078,13877],{"class":163},[112,15080,11070],{"class":129},[112,15082,15083],{"class":114,"line":205},[112,15084,147],{"emptyLinePlaceholder":146},[112,15086,15087,15089,15091],{"class":114,"line":241},[112,15088,11079],{"class":129},[112,15090,11136],{"class":163},[112,15092,630],{"class":129},[112,15094,15095,15097,15099,15101,15103],{"class":114,"line":247},[112,15096,13896],{"class":163},[112,15098,13899],{"class":129},[112,15100,2243],{"class":125},[112,15102,10478],{"class":156},[112,15104,1294],{"class":129},[112,15106,15107,15109,15111,15113,15115],{"class":114,"line":351},[112,15108,3973],{"class":125},[112,15110,10318],{"class":156},[112,15112,13914],{"class":129},[112,15114,13917],{"class":163},[112,15116,630],{"class":129},[112,15118,15119],{"class":114,"line":361},[112,15120,3232],{"class":129},[112,15122,15123],{"class":114,"line":367},[112,15124,1452],{"class":129},[8992,15126,15128],{"id":15127},"service-ビジネスロジック","Service - ビジネスロジック",[103,15130,15132],{"className":105,"code":15131,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fservices\u002Fapp.service.ts\nimport { Injectable } from '@nestjs\u002Fcommon'\n\n@Injectable()\nexport class AppService {\n  getHello(): string {\n    return 'Hello World!'\n  }\n}\n",[90,15133,15134,15139,15150,15154,15162,15172,15184,15191,15195],{"__ignoreMap":108},[112,15135,15136],{"class":114,"line":115},[112,15137,15138],{"class":118},"\u002F\u002F src\u002Fservices\u002Fapp.service.ts\n",[112,15140,15141,15143,15146,15148],{"class":114,"line":122},[112,15142,126],{"class":125},[112,15144,15145],{"class":129}," { Injectable } ",[112,15147,133],{"class":125},[112,15149,9868],{"class":136},[112,15151,15152],{"class":114,"line":143},[112,15153,147],{"emptyLinePlaceholder":146},[112,15155,15156,15158,15160],{"class":114,"line":150},[112,15157,9901],{"class":129},[112,15159,10076],{"class":163},[112,15161,630],{"class":129},[112,15163,15164,15166,15168,15170],{"class":114,"line":170},[112,15165,288],{"class":125},[112,15167,9931],{"class":125},[112,15169,13877],{"class":163},[112,15171,1294],{"class":129},[112,15173,15174,15176,15178,15180,15182],{"class":114,"line":182},[112,15175,13896],{"class":163},[112,15177,13899],{"class":129},[112,15179,2243],{"class":125},[112,15181,10478],{"class":156},[112,15183,1294],{"class":129},[112,15185,15186,15188],{"class":114,"line":193},[112,15187,3973],{"class":125},[112,15189,15190],{"class":136}," 'Hello World!'\n",[112,15192,15193],{"class":114,"line":205},[112,15194,3232],{"class":129},[112,15196,15197],{"class":114,"line":241},[112,15198,1452],{"class":129},[11,15200,15202],{"id":15201},"mysql環境の構築","MySQL環境の構築",[83,15204,15206],{"id":15205},"docker-composeymlの作成","docker-compose.ymlの作成",[103,15208,15210],{"className":9132,"code":15209,"language":9134,"meta":108,"style":108},"# docker-compose.yml\nversion: '3'\n\nservices:\n  db:\n    image: mysql:8.0\n    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci\n    container_name: meetup_db_container\n    volumes:\n      - mysql-data-volume:\u002Fvar\u002Flib\u002Fmysql\n    ports:\n      - \"3306:3306\"\n    environment:\n      TZ: 'Asia\u002FTokyo'\n      MYSQL_ROOT_PASSWORD: password\n      MYSQL_DATABASE: meetup\n      MYSQL_USER: app\n      MYSQL_PASSWORD: secret\n\nvolumes:\n  mysql-data-volume:\n",[90,15211,15212,15216,15225,15229,15235,15242,15251,15260,15270,15276,15283,15289,15296,15302,15312,15321,15331,15341,15351,15355,15361],{"__ignoreMap":108},[112,15213,15214],{"class":114,"line":115},[112,15215,12901],{"class":118},[112,15217,15218,15220,15222],{"class":114,"line":122},[112,15219,9142],{"class":9141},[112,15221,567],{"class":129},[112,15223,15224],{"class":136},"'3'\n",[112,15226,15227],{"class":114,"line":143},[112,15228,147],{"emptyLinePlaceholder":146},[112,15230,15231,15233],{"class":114,"line":150},[112,15232,9156],{"class":9141},[112,15234,5004],{"class":129},[112,15236,15237,15240],{"class":114,"line":170},[112,15238,15239],{"class":9141},"  db",[112,15241,5004],{"class":129},[112,15243,15244,15246,15248],{"class":114,"line":182},[112,15245,9170],{"class":9141},[112,15247,567],{"class":129},[112,15249,15250],{"class":136},"mysql:8.0\n",[112,15252,15253,15255,15257],{"class":114,"line":193},[112,15254,9219],{"class":9141},[112,15256,567],{"class":129},[112,15258,15259],{"class":136},"mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci\n",[112,15261,15262,15265,15267],{"class":114,"line":205},[112,15263,15264],{"class":9141},"    container_name",[112,15266,567],{"class":129},[112,15268,15269],{"class":136},"meetup_db_container\n",[112,15271,15272,15274],{"class":114,"line":241},[112,15273,9197],{"class":9141},[112,15275,5004],{"class":129},[112,15277,15278,15280],{"class":114,"line":247},[112,15279,9204],{"class":129},[112,15281,15282],{"class":136},"mysql-data-volume:\u002Fvar\u002Flib\u002Fmysql\n",[112,15284,15285,15287],{"class":114,"line":351},[112,15286,12979],{"class":9141},[112,15288,5004],{"class":129},[112,15290,15291,15293],{"class":114,"line":361},[112,15292,9204],{"class":129},[112,15294,15295],{"class":136},"\"3306:3306\"\n",[112,15297,15298,15300],{"class":114,"line":367},[112,15299,9180],{"class":9141},[112,15301,5004],{"class":129},[112,15303,15304,15307,15309],{"class":114,"line":373},[112,15305,15306],{"class":9141},"      TZ",[112,15308,567],{"class":129},[112,15310,15311],{"class":136},"'Asia\u002FTokyo'\n",[112,15313,15314,15317,15319],{"class":114,"line":379},[112,15315,15316],{"class":9141},"      MYSQL_ROOT_PASSWORD",[112,15318,567],{"class":129},[112,15320,9192],{"class":136},[112,15322,15323,15326,15328],{"class":114,"line":1302},[112,15324,15325],{"class":9141},"      MYSQL_DATABASE",[112,15327,567],{"class":129},[112,15329,15330],{"class":136},"meetup\n",[112,15332,15333,15336,15338],{"class":114,"line":1502},[112,15334,15335],{"class":9141},"      MYSQL_USER",[112,15337,567],{"class":129},[112,15339,15340],{"class":136},"app\n",[112,15342,15343,15346,15348],{"class":114,"line":1507},[112,15344,15345],{"class":9141},"      MYSQL_PASSWORD",[112,15347,567],{"class":129},[112,15349,15350],{"class":136},"secret\n",[112,15352,15353],{"class":114,"line":1512},[112,15354,147],{"emptyLinePlaceholder":146},[112,15356,15357,15359],{"class":114,"line":1518},[112,15358,9461],{"class":9141},[112,15360,5004],{"class":129},[112,15362,15363,15366],{"class":114,"line":1524},[112,15364,15365],{"class":9141},"  mysql-data-volume",[112,15367,5004],{"class":129},[83,15369,15370],{"id":15370},"コンテナの起動",[103,15372,15374],{"className":771,"code":15373,"language":773,"meta":108,"style":108},"docker compose up -d\n\n# 起動確認\ndocker compose ps\n\n# コンテナに入って接続確認\ndocker compose exec db bash\nmysql -u root -p\n# password: password\n",[90,15375,15376,15390,15394,15399,15408,15412,15417,15432,15445],{"__ignoreMap":108},[112,15377,15378,15381,15384,15387],{"class":114,"line":115},[112,15379,15380],{"class":163},"docker",[112,15382,15383],{"class":136}," compose",[112,15385,15386],{"class":136}," up",[112,15388,15389],{"class":156}," -d\n",[112,15391,15392],{"class":114,"line":122},[112,15393,147],{"emptyLinePlaceholder":146},[112,15395,15396],{"class":114,"line":143},[112,15397,15398],{"class":118},"# 起動確認\n",[112,15400,15401,15403,15405],{"class":114,"line":150},[112,15402,15380],{"class":163},[112,15404,15383],{"class":136},[112,15406,15407],{"class":136}," ps\n",[112,15409,15410],{"class":114,"line":170},[112,15411,147],{"emptyLinePlaceholder":146},[112,15413,15414],{"class":114,"line":182},[112,15415,15416],{"class":118},"# コンテナに入って接続確認\n",[112,15418,15419,15421,15423,15426,15429],{"class":114,"line":193},[112,15420,15380],{"class":163},[112,15422,15383],{"class":136},[112,15424,15425],{"class":136}," exec",[112,15427,15428],{"class":136}," db",[112,15430,15431],{"class":136}," bash\n",[112,15433,15434,15437,15439,15442],{"class":114,"line":205},[112,15435,15436],{"class":163},"mysql",[112,15438,6977],{"class":156},[112,15440,15441],{"class":136}," root",[112,15443,15444],{"class":156}," -p\n",[112,15446,15447],{"class":114,"line":241},[112,15448,15449],{"class":118},"# password: password\n",[11,15451,15453],{"id":15452},"typeormの設定","TypeORMの設定",[83,15455,15456],{"id":15456},"必要なパッケージのインストール",[103,15458,15460],{"className":771,"code":15459,"language":773,"meta":108,"style":108},"npm install --save @nestjs\u002Ftypeorm typeorm@0.2 mysql2\nnpm install --save @nestjs\u002Fconfig\n",[90,15461,15462,15480],{"__ignoreMap":108},[112,15463,15464,15466,15468,15471,15474,15477],{"class":114,"line":115},[112,15465,6832],{"class":163},[112,15467,7883],{"class":136},[112,15469,15470],{"class":156}," --save",[112,15472,15473],{"class":136}," @nestjs\u002Ftypeorm",[112,15475,15476],{"class":136}," typeorm@0.2",[112,15478,15479],{"class":136}," mysql2\n",[112,15481,15482,15484,15486,15488],{"class":114,"line":122},[112,15483,6832],{"class":163},[112,15485,7883],{"class":136},[112,15487,15470],{"class":156},[112,15489,15490],{"class":136}," @nestjs\u002Fconfig\n",[83,15492,15493],{"id":15493},"設定ファイルの作成",[103,15495,15497],{"className":105,"code":15496,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fconfig\u002Fconfiguration.ts\nexport default () => ({\n  nodeEnv: process.env.NODE_ENV || 'development',\n  server: {\n    port: parseInt(process.env.PORT) || 3000,\n    hostName: process.env.hostname || 'localhost:3000',\n  },\n  database: {\n    host: process.env.DB_HOST || 'localhost',\n    port: parseInt(process.env.DB_PORT) || 3306,\n    user: process.env.DB_USERNAME || 'root',\n    pass: process.env.DB_PASSWORD || 'password',\n    name: process.env.DB_NAME || 'meetup',\n  },\n})\n",[90,15498,15499,15504,15516,15530,15535,15558,15570,15574,15579,15594,15614,15629,15644,15659,15663],{"__ignoreMap":108},[112,15500,15501],{"class":114,"line":115},[112,15502,15503],{"class":118},"\u002F\u002F src\u002Fconfig\u002Fconfiguration.ts\n",[112,15505,15506,15508,15510,15512,15514],{"class":114,"line":122},[112,15507,288],{"class":125},[112,15509,291],{"class":125},[112,15511,3802],{"class":129},[112,15513,229],{"class":125},[112,15515,2097],{"class":129},[112,15517,15518,15521,15523,15525,15528],{"class":114,"line":143},[112,15519,15520],{"class":129},"  nodeEnv: process.env.",[112,15522,7988],{"class":156},[112,15524,10852],{"class":125},[112,15526,15527],{"class":136}," 'development'",[112,15529,179],{"class":129},[112,15531,15532],{"class":114,"line":150},[112,15533,15534],{"class":129},"  server: {\n",[112,15536,15537,15540,15543,15546,15549,15551,15553,15556],{"class":114,"line":170},[112,15538,15539],{"class":129},"    port: ",[112,15541,15542],{"class":163},"parseInt",[112,15544,15545],{"class":129},"(process.env.",[112,15547,15548],{"class":156},"PORT",[112,15550,226],{"class":129},[112,15552,4284],{"class":125},[112,15554,15555],{"class":156}," 3000",[112,15557,179],{"class":129},[112,15559,15560,15563,15565,15568],{"class":114,"line":182},[112,15561,15562],{"class":129},"    hostName: process.env.hostname ",[112,15564,4284],{"class":125},[112,15566,15567],{"class":136}," 'localhost:3000'",[112,15569,179],{"class":129},[112,15571,15572],{"class":114,"line":193},[112,15573,376],{"class":129},[112,15575,15576],{"class":114,"line":205},[112,15577,15578],{"class":129},"  database: {\n",[112,15580,15581,15584,15587,15589,15592],{"class":114,"line":241},[112,15582,15583],{"class":129},"    host: process.env.",[112,15585,15586],{"class":156},"DB_HOST",[112,15588,10852],{"class":125},[112,15590,15591],{"class":136}," 'localhost'",[112,15593,179],{"class":129},[112,15595,15596,15598,15600,15602,15605,15607,15609,15612],{"class":114,"line":247},[112,15597,15539],{"class":129},[112,15599,15542],{"class":163},[112,15601,15545],{"class":129},[112,15603,15604],{"class":156},"DB_PORT",[112,15606,226],{"class":129},[112,15608,4284],{"class":125},[112,15610,15611],{"class":156}," 3306",[112,15613,179],{"class":129},[112,15615,15616,15619,15622,15624,15627],{"class":114,"line":351},[112,15617,15618],{"class":129},"    user: process.env.",[112,15620,15621],{"class":156},"DB_USERNAME",[112,15623,10852],{"class":125},[112,15625,15626],{"class":136}," 'root'",[112,15628,179],{"class":129},[112,15630,15631,15634,15637,15639,15642],{"class":114,"line":361},[112,15632,15633],{"class":129},"    pass: process.env.",[112,15635,15636],{"class":156},"DB_PASSWORD",[112,15638,10852],{"class":125},[112,15640,15641],{"class":136}," 'password'",[112,15643,179],{"class":129},[112,15645,15646,15649,15652,15654,15657],{"class":114,"line":367},[112,15647,15648],{"class":129},"    name: process.env.",[112,15650,15651],{"class":156},"DB_NAME",[112,15653,10852],{"class":125},[112,15655,15656],{"class":136}," 'meetup'",[112,15658,179],{"class":129},[112,15660,15661],{"class":114,"line":373},[112,15662,376],{"class":129},[112,15664,15665],{"class":114,"line":379},[112,15666,8436],{"class":129},[83,15668,15670],{"id":15669},"appmoduleへの統合","AppModuleへの統合",[103,15672,15674],{"className":105,"code":15673,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fmodules\u002Fapp.module.ts\nimport { Module } from '@nestjs\u002Fcommon'\nimport { ConfigModule, ConfigService } from '@nestjs\u002Fconfig'\nimport { TypeOrmModule } from '@nestjs\u002Ftypeorm'\nimport configuration from '..\u002Fconfig\u002Fconfiguration'\nimport { join } from 'path'\n\n@Module({\n  imports: [\n    ConfigModule.forRoot({\n      isGlobal: true,\n      load: [configuration],\n    }),\n    TypeOrmModule.forRootAsync({\n      imports: [ConfigModule],\n      useFactory: (configService: ConfigService) => ({\n        type: 'mysql',\n        host: configService.get('database.host'),\n        port: configService.get('database.port'),\n        username: configService.get('database.user'),\n        password: configService.get('database.pass'),\n        database: configService.get('database.name'),\n        entities: [join(__dirname, '..\u002Fentities\u002F*.entity.{ts,js}')],\n        synchronize: false,\n        logging: configService.get('nodeEnv') === 'development',\n      }),\n      inject: [ConfigService],\n    }),\n  ],\n  \u002F\u002F ...\n})\nexport class AppModule {}\n",[90,15675,15676,15680,15690,15701,15713,15725,15737,15741,15749,15754,15764,15773,15778,15782,15792,15797,15818,15828,15843,15857,15871,15885,15899,15915,15924,15944,15948,15953,15957,15961,15966,15970],{"__ignoreMap":108},[112,15677,15678],{"class":114,"line":115},[112,15679,14930],{"class":118},[112,15681,15682,15684,15686,15688],{"class":114,"line":122},[112,15683,126],{"class":125},[112,15685,9863],{"class":129},[112,15687,133],{"class":125},[112,15689,9868],{"class":136},[112,15691,15692,15694,15697,15699],{"class":114,"line":143},[112,15693,126],{"class":125},[112,15695,15696],{"class":129}," { ConfigModule, ConfigService } ",[112,15698,133],{"class":125},[112,15700,9880],{"class":136},[112,15702,15703,15705,15708,15710],{"class":114,"line":150},[112,15704,126],{"class":125},[112,15706,15707],{"class":129}," { TypeOrmModule } ",[112,15709,133],{"class":125},[112,15711,15712],{"class":136}," '@nestjs\u002Ftypeorm'\n",[112,15714,15715,15717,15720,15722],{"class":114,"line":170},[112,15716,126],{"class":125},[112,15718,15719],{"class":129}," configuration ",[112,15721,133],{"class":125},[112,15723,15724],{"class":136}," '..\u002Fconfig\u002Fconfiguration'\n",[112,15726,15727,15729,15732,15734],{"class":114,"line":182},[112,15728,126],{"class":125},[112,15730,15731],{"class":129}," { join } ",[112,15733,133],{"class":125},[112,15735,15736],{"class":136}," 'path'\n",[112,15738,15739],{"class":114,"line":193},[112,15740,147],{"emptyLinePlaceholder":146},[112,15742,15743,15745,15747],{"class":114,"line":205},[112,15744,9901],{"class":129},[112,15746,9851],{"class":163},[112,15748,167],{"class":129},[112,15750,15751],{"class":114,"line":241},[112,15752,15753],{"class":129},"  imports: [\n",[112,15755,15756,15759,15762],{"class":114,"line":247},[112,15757,15758],{"class":129},"    ConfigModule.",[112,15760,15761],{"class":163},"forRoot",[112,15763,167],{"class":129},[112,15765,15766,15769,15771],{"class":114,"line":351},[112,15767,15768],{"class":129},"      isGlobal: ",[112,15770,4345],{"class":156},[112,15772,179],{"class":129},[112,15774,15775],{"class":114,"line":361},[112,15776,15777],{"class":129},"      load: [configuration],\n",[112,15779,15780],{"class":114,"line":367},[112,15781,370],{"class":129},[112,15783,15784,15787,15790],{"class":114,"line":373},[112,15785,15786],{"class":129},"    TypeOrmModule.",[112,15788,15789],{"class":163},"forRootAsync",[112,15791,167],{"class":129},[112,15793,15794],{"class":114,"line":379},[112,15795,15796],{"class":129},"      imports: [ConfigModule],\n",[112,15798,15799,15802,15805,15808,15810,15812,15814,15816],{"class":114,"line":1302},[112,15800,15801],{"class":163},"      useFactory",[112,15803,15804],{"class":129},": (",[112,15806,15807],{"class":222},"configService",[112,15809,2243],{"class":125},[112,15811,10141],{"class":163},[112,15813,226],{"class":129},[112,15815,229],{"class":125},[112,15817,2097],{"class":129},[112,15819,15820,15823,15826],{"class":114,"line":1502},[112,15821,15822],{"class":129},"        type: ",[112,15824,15825],{"class":136},"'mysql'",[112,15827,179],{"class":129},[112,15829,15830,15833,15835,15837,15840],{"class":114,"line":1507},[112,15831,15832],{"class":129},"        host: configService.",[112,15834,3344],{"class":163},[112,15836,425],{"class":129},[112,15838,15839],{"class":136},"'database.host'",[112,15841,15842],{"class":129},"),\n",[112,15844,15845,15848,15850,15852,15855],{"class":114,"line":1512},[112,15846,15847],{"class":129},"        port: configService.",[112,15849,3344],{"class":163},[112,15851,425],{"class":129},[112,15853,15854],{"class":136},"'database.port'",[112,15856,15842],{"class":129},[112,15858,15859,15862,15864,15866,15869],{"class":114,"line":1518},[112,15860,15861],{"class":129},"        username: configService.",[112,15863,3344],{"class":163},[112,15865,425],{"class":129},[112,15867,15868],{"class":136},"'database.user'",[112,15870,15842],{"class":129},[112,15872,15873,15876,15878,15880,15883],{"class":114,"line":1524},[112,15874,15875],{"class":129},"        password: configService.",[112,15877,3344],{"class":163},[112,15879,425],{"class":129},[112,15881,15882],{"class":136},"'database.pass'",[112,15884,15842],{"class":129},[112,15886,15887,15890,15892,15894,15897],{"class":114,"line":1530},[112,15888,15889],{"class":129},"        database: configService.",[112,15891,3344],{"class":163},[112,15893,425],{"class":129},[112,15895,15896],{"class":136},"'database.name'",[112,15898,15842],{"class":129},[112,15900,15901,15904,15906,15909,15912],{"class":114,"line":1536},[112,15902,15903],{"class":129},"        entities: [",[112,15905,535],{"class":163},[112,15907,15908],{"class":129},"(__dirname, ",[112,15910,15911],{"class":136},"'..\u002Fentities\u002F*.entity.{ts,js}'",[112,15913,15914],{"class":129},")],\n",[112,15916,15917,15920,15922],{"class":114,"line":1542},[112,15918,15919],{"class":129},"        synchronize: ",[112,15921,10359],{"class":156},[112,15923,179],{"class":129},[112,15925,15926,15929,15931,15933,15936,15938,15940,15942],{"class":114,"line":2402},[112,15927,15928],{"class":129},"        logging: configService.",[112,15930,3344],{"class":163},[112,15932,425],{"class":129},[112,15934,15935],{"class":136},"'nodeEnv'",[112,15937,226],{"class":129},[112,15939,3624],{"class":125},[112,15941,15527],{"class":136},[112,15943,179],{"class":129},[112,15945,15946],{"class":114,"line":2407},[112,15947,364],{"class":129},[112,15949,15950],{"class":114,"line":2413},[112,15951,15952],{"class":129},"      inject: [ConfigService],\n",[112,15954,15955],{"class":114,"line":2446},[112,15956,370],{"class":129},[112,15958,15959],{"class":114,"line":2451},[112,15960,1887],{"class":129},[112,15962,15963],{"class":114,"line":2464},[112,15964,15965],{"class":118},"  \u002F\u002F ...\n",[112,15967,15968],{"class":114,"line":2481},[112,15969,8436],{"class":129},[112,15971,15972,15974,15976,15978],{"class":114,"line":2486},[112,15973,288],{"class":125},[112,15975,9931],{"class":125},[112,15977,15003],{"class":163},[112,15979,9937],{"class":129},[11,15981,15983],{"id":15982},"crudアプリケーションの実装","CRUDアプリケーションの実装",[83,15985,15986],{"id":15986},"リソースの生成",[103,15988,15990],{"className":771,"code":15989,"language":773,"meta":108,"style":108},"nest generate resource users\n# REST APIを選択\n# CRUD entry pointsを有効化\n",[90,15991,15992,16005,16010],{"__ignoreMap":108},[112,15993,15994,15996,15999,16002],{"class":114,"line":115},[112,15995,13154],{"class":163},[112,15997,15998],{"class":136}," generate",[112,16000,16001],{"class":136}," resource",[112,16003,16004],{"class":136}," users\n",[112,16006,16007],{"class":114,"line":122},[112,16008,16009],{"class":118},"# REST APIを選択\n",[112,16011,16012],{"class":114,"line":143},[112,16013,16014],{"class":118},"# CRUD entry pointsを有効化\n",[83,16016,16018],{"id":16017},"entity定義","Entity定義",[103,16020,16022],{"className":105,"code":16021,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fentities\u002Fuser.entity.ts\nimport {\n  Column,\n  CreateDateColumn,\n  DeleteDateColumn,\n  Entity,\n  PrimaryGeneratedColumn,\n  Timestamp,\n  UpdateDateColumn,\n} from 'typeorm'\n\n@Entity('users')\nexport class User {\n  @PrimaryGeneratedColumn({\n    name: 'id',\n    unsigned: true,\n    type: 'bigint',\n    comment: 'ユーザーID',\n  })\n  readonly id: number\n\n  @Column({ type: 'varchar', length: 255, comment: 'ユーザー名' })\n  name: string\n\n  @Column({ type: 'varchar', length: 255, comment: 'メールアドレス', unique: true })\n  email: string\n\n  @Column({ type: 'varchar', length: 255, comment: 'パスワード' })\n  password: string\n\n  @CreateDateColumn({ comment: '登録日時' })\n  readonly ins_ts?: Timestamp\n\n  @UpdateDateColumn({ comment: '最終更新日時' })\n  readonly upd_ts?: Timestamp\n\n  @DeleteDateColumn({ comment: '削除日時' })\n  readonly delete_ts?: Timestamp\n\n  constructor(name: string, email: string, password: string) {\n    this.name = name\n    this.email = email\n    this.password = password\n  }\n}\n",[90,16023,16024,16029,16035,16040,16045,16050,16055,16060,16065,16070,16079,16083,16096,16107,16116,16126,16135,16145,16155,16159,16172,16176,16203,16211,16215,16241,16249,16253,16274,16282,16286,16301,16313,16317,16331,16342,16346,16360,16371,16375,16407,16419,16431,16443,16447],{"__ignoreMap":108},[112,16025,16026],{"class":114,"line":115},[112,16027,16028],{"class":118},"\u002F\u002F src\u002Fentities\u002Fuser.entity.ts\n",[112,16030,16031,16033],{"class":114,"line":122},[112,16032,126],{"class":125},[112,16034,1294],{"class":129},[112,16036,16037],{"class":114,"line":143},[112,16038,16039],{"class":129},"  Column,\n",[112,16041,16042],{"class":114,"line":150},[112,16043,16044],{"class":129},"  CreateDateColumn,\n",[112,16046,16047],{"class":114,"line":170},[112,16048,16049],{"class":129},"  DeleteDateColumn,\n",[112,16051,16052],{"class":114,"line":182},[112,16053,16054],{"class":129},"  Entity,\n",[112,16056,16057],{"class":114,"line":193},[112,16058,16059],{"class":129},"  PrimaryGeneratedColumn,\n",[112,16061,16062],{"class":114,"line":205},[112,16063,16064],{"class":129},"  Timestamp,\n",[112,16066,16067],{"class":114,"line":241},[112,16068,16069],{"class":129},"  UpdateDateColumn,\n",[112,16071,16072,16074,16076],{"class":114,"line":247},[112,16073,10929],{"class":129},[112,16075,133],{"class":125},[112,16077,16078],{"class":136}," 'typeorm'\n",[112,16080,16081],{"class":114,"line":351},[112,16082,147],{"emptyLinePlaceholder":146},[112,16084,16085,16087,16090,16092,16094],{"class":114,"line":361},[112,16086,9901],{"class":129},[112,16088,16089],{"class":163},"Entity",[112,16091,425],{"class":129},[112,16093,11037],{"class":136},[112,16095,431],{"class":129},[112,16097,16098,16100,16102,16105],{"class":114,"line":367},[112,16099,288],{"class":125},[112,16101,9931],{"class":125},[112,16103,16104],{"class":163}," User",[112,16106,1294],{"class":129},[112,16108,16109,16111,16114],{"class":114,"line":373},[112,16110,11079],{"class":129},[112,16112,16113],{"class":163},"PrimaryGeneratedColumn",[112,16115,167],{"class":129},[112,16117,16118,16121,16124],{"class":114,"line":379},[112,16119,16120],{"class":129},"    name: ",[112,16122,16123],{"class":136},"'id'",[112,16125,179],{"class":129},[112,16127,16128,16131,16133],{"class":114,"line":1302},[112,16129,16130],{"class":129},"    unsigned: ",[112,16132,4345],{"class":156},[112,16134,179],{"class":129},[112,16136,16137,16140,16143],{"class":114,"line":1502},[112,16138,16139],{"class":129},"    type: ",[112,16141,16142],{"class":136},"'bigint'",[112,16144,179],{"class":129},[112,16146,16147,16150,16153],{"class":114,"line":1507},[112,16148,16149],{"class":129},"    comment: ",[112,16151,16152],{"class":136},"'ユーザーID'",[112,16154,179],{"class":129},[112,16156,16157],{"class":114,"line":1512},[112,16158,8004],{"class":129},[112,16160,16161,16164,16167,16169],{"class":114,"line":1518},[112,16162,16163],{"class":125},"  readonly",[112,16165,16166],{"class":222}," id",[112,16168,2243],{"class":125},[112,16170,16171],{"class":156}," number\n",[112,16173,16174],{"class":114,"line":1524},[112,16175,147],{"emptyLinePlaceholder":146},[112,16177,16178,16180,16183,16186,16189,16192,16195,16198,16201],{"class":114,"line":1530},[112,16179,11079],{"class":129},[112,16181,16182],{"class":163},"Column",[112,16184,16185],{"class":129},"({ type: ",[112,16187,16188],{"class":136},"'varchar'",[112,16190,16191],{"class":129},", length: ",[112,16193,16194],{"class":156},"255",[112,16196,16197],{"class":129},", comment: ",[112,16199,16200],{"class":136},"'ユーザー名'",[112,16202,11585],{"class":129},[112,16204,16205,16207,16209],{"class":114,"line":1536},[112,16206,10020],{"class":222},[112,16208,2243],{"class":125},[112,16210,10006],{"class":156},[112,16212,16213],{"class":114,"line":1542},[112,16214,147],{"emptyLinePlaceholder":146},[112,16216,16217,16219,16221,16223,16225,16227,16229,16231,16234,16237,16239],{"class":114,"line":2402},[112,16218,11079],{"class":129},[112,16220,16182],{"class":163},[112,16222,16185],{"class":129},[112,16224,16188],{"class":136},[112,16226,16191],{"class":129},[112,16228,16194],{"class":156},[112,16230,16197],{"class":129},[112,16232,16233],{"class":136},"'メールアドレス'",[112,16235,16236],{"class":129},", unique: ",[112,16238,4345],{"class":156},[112,16240,11585],{"class":129},[112,16242,16243,16245,16247],{"class":114,"line":2407},[112,16244,10001],{"class":222},[112,16246,2243],{"class":125},[112,16248,10006],{"class":156},[112,16250,16251],{"class":114,"line":2413},[112,16252,147],{"emptyLinePlaceholder":146},[112,16254,16255,16257,16259,16261,16263,16265,16267,16269,16272],{"class":114,"line":2446},[112,16256,11079],{"class":129},[112,16258,16182],{"class":163},[112,16260,16185],{"class":129},[112,16262,16188],{"class":136},[112,16264,16191],{"class":129},[112,16266,16194],{"class":156},[112,16268,16197],{"class":129},[112,16270,16271],{"class":136},"'パスワード'",[112,16273,11585],{"class":129},[112,16275,16276,16278,16280],{"class":114,"line":2451},[112,16277,10011],{"class":222},[112,16279,2243],{"class":125},[112,16281,10006],{"class":156},[112,16283,16284],{"class":114,"line":2464},[112,16285,147],{"emptyLinePlaceholder":146},[112,16287,16288,16290,16293,16296,16299],{"class":114,"line":2481},[112,16289,11079],{"class":129},[112,16291,16292],{"class":163},"CreateDateColumn",[112,16294,16295],{"class":129},"({ comment: ",[112,16297,16298],{"class":136},"'登録日時'",[112,16300,11585],{"class":129},[112,16302,16303,16305,16308,16310],{"class":114,"line":2486},[112,16304,16163],{"class":125},[112,16306,16307],{"class":222}," ins_ts",[112,16309,10023],{"class":125},[112,16311,16312],{"class":163}," Timestamp\n",[112,16314,16315],{"class":114,"line":3229},[112,16316,147],{"emptyLinePlaceholder":146},[112,16318,16319,16321,16324,16326,16329],{"class":114,"line":3235},[112,16320,11079],{"class":129},[112,16322,16323],{"class":163},"UpdateDateColumn",[112,16325,16295],{"class":129},[112,16327,16328],{"class":136},"'最終更新日時'",[112,16330,11585],{"class":129},[112,16332,16333,16335,16338,16340],{"class":114,"line":3657},[112,16334,16163],{"class":125},[112,16336,16337],{"class":222}," upd_ts",[112,16339,10023],{"class":125},[112,16341,16312],{"class":163},[112,16343,16344],{"class":114,"line":3662},[112,16345,147],{"emptyLinePlaceholder":146},[112,16347,16348,16350,16353,16355,16358],{"class":114,"line":3667},[112,16349,11079],{"class":129},[112,16351,16352],{"class":163},"DeleteDateColumn",[112,16354,16295],{"class":129},[112,16356,16357],{"class":136},"'削除日時'",[112,16359,11585],{"class":129},[112,16361,16362,16364,16367,16369],{"class":114,"line":3680},[112,16363,16163],{"class":125},[112,16365,16366],{"class":222}," delete_ts",[112,16368,10023],{"class":125},[112,16370,16312],{"class":163},[112,16372,16373],{"class":114,"line":3692},[112,16374,147],{"emptyLinePlaceholder":146},[112,16376,16377,16379,16381,16384,16386,16388,16390,16393,16395,16397,16399,16401,16403,16405],{"class":114,"line":3716},[112,16378,10128],{"class":125},[112,16380,425],{"class":129},[112,16382,16383],{"class":222},"name",[112,16385,2243],{"class":125},[112,16387,10478],{"class":156},[112,16389,447],{"class":129},[112,16391,16392],{"class":222},"email",[112,16394,2243],{"class":125},[112,16396,10478],{"class":156},[112,16398,447],{"class":129},[112,16400,14304],{"class":222},[112,16402,2243],{"class":125},[112,16404,10478],{"class":156},[112,16406,1969],{"class":129},[112,16408,16409,16411,16414,16416],{"class":114,"line":3737},[112,16410,10148],{"class":156},[112,16412,16413],{"class":129},".name ",[112,16415,576],{"class":125},[112,16417,16418],{"class":129}," name\n",[112,16420,16421,16423,16426,16428],{"class":114,"line":3742},[112,16422,10148],{"class":156},[112,16424,16425],{"class":129},".email ",[112,16427,576],{"class":125},[112,16429,16430],{"class":129}," email\n",[112,16432,16433,16435,16438,16440],{"class":114,"line":3747},[112,16434,10148],{"class":156},[112,16436,16437],{"class":129},".password ",[112,16439,576],{"class":125},[112,16441,16442],{"class":129}," password\n",[112,16444,16445],{"class":114,"line":3752},[112,16446,3232],{"class":129},[112,16448,16449],{"class":114,"line":3758},[112,16450,1452],{"class":129},[83,16452,16454],{"id":16453},"typeorm設定ファイル","TypeORM設定ファイル",[103,16456,16458],{"className":105,"code":16457,"language":107,"meta":108,"style":108},"\u002F\u002F ormconfig.ts\nmodule.exports = {\n  type: 'mysql',\n  host: process.env.DB_HOST || 'localhost',\n  port: process.env.DB_PORT || '3306',\n  username: process.env.DB_USERNAME || 'root',\n  password: process.env.DB_PASSWORD || 'password',\n  database: process.env.DB_NAME || 'meetup',\n  synchronize: false,\n  logging: true,\n  entities: ['src\u002Fentities\u002F*.ts'],\n  migrations: ['src\u002Fdatabases\u002Fmigrations\u002F*.ts'],\n  cli: {\n    migrationsDir: 'src\u002Fdatabases\u002Fmigrations',\n    entitiesDir: 'src\u002Fentities',\n  },\n}\n",[90,16459,16460,16465,16478,16486,16499,16513,16526,16539,16552,16561,16570,16580,16590,16595,16605,16615,16619],{"__ignoreMap":108},[112,16461,16462],{"class":114,"line":115},[112,16463,16464],{"class":118},"\u002F\u002F ormconfig.ts\n",[112,16466,16467,16469,16471,16474,16476],{"class":114,"line":122},[112,16468,9850],{"class":156},[112,16470,2124],{"class":129},[112,16472,16473],{"class":156},"exports",[112,16475,160],{"class":125},[112,16477,1294],{"class":129},[112,16479,16480,16482,16484],{"class":114,"line":143},[112,16481,173],{"class":129},[112,16483,15825],{"class":136},[112,16485,179],{"class":129},[112,16487,16488,16491,16493,16495,16497],{"class":114,"line":150},[112,16489,16490],{"class":129},"  host: process.env.",[112,16492,15586],{"class":156},[112,16494,10852],{"class":125},[112,16496,15591],{"class":136},[112,16498,179],{"class":129},[112,16500,16501,16504,16506,16508,16511],{"class":114,"line":170},[112,16502,16503],{"class":129},"  port: process.env.",[112,16505,15604],{"class":156},[112,16507,10852],{"class":125},[112,16509,16510],{"class":136}," '3306'",[112,16512,179],{"class":129},[112,16514,16515,16518,16520,16522,16524],{"class":114,"line":182},[112,16516,16517],{"class":129},"  username: process.env.",[112,16519,15621],{"class":156},[112,16521,10852],{"class":125},[112,16523,15626],{"class":136},[112,16525,179],{"class":129},[112,16527,16528,16531,16533,16535,16537],{"class":114,"line":193},[112,16529,16530],{"class":129},"  password: process.env.",[112,16532,15636],{"class":156},[112,16534,10852],{"class":125},[112,16536,15641],{"class":136},[112,16538,179],{"class":129},[112,16540,16541,16544,16546,16548,16550],{"class":114,"line":205},[112,16542,16543],{"class":129},"  database: process.env.",[112,16545,15651],{"class":156},[112,16547,10852],{"class":125},[112,16549,15656],{"class":136},[112,16551,179],{"class":129},[112,16553,16554,16557,16559],{"class":114,"line":241},[112,16555,16556],{"class":129},"  synchronize: ",[112,16558,10359],{"class":156},[112,16560,179],{"class":129},[112,16562,16563,16566,16568],{"class":114,"line":247},[112,16564,16565],{"class":129},"  logging: ",[112,16567,4345],{"class":156},[112,16569,179],{"class":129},[112,16571,16572,16575,16578],{"class":114,"line":351},[112,16573,16574],{"class":129},"  entities: [",[112,16576,16577],{"class":136},"'src\u002Fentities\u002F*.ts'",[112,16579,746],{"class":129},[112,16581,16582,16585,16588],{"class":114,"line":361},[112,16583,16584],{"class":129},"  migrations: [",[112,16586,16587],{"class":136},"'src\u002Fdatabases\u002Fmigrations\u002F*.ts'",[112,16589,746],{"class":129},[112,16591,16592],{"class":114,"line":367},[112,16593,16594],{"class":129},"  cli: {\n",[112,16596,16597,16600,16603],{"class":114,"line":373},[112,16598,16599],{"class":129},"    migrationsDir: ",[112,16601,16602],{"class":136},"'src\u002Fdatabases\u002Fmigrations'",[112,16604,179],{"class":129},[112,16606,16607,16610,16613],{"class":114,"line":379},[112,16608,16609],{"class":129},"    entitiesDir: ",[112,16611,16612],{"class":136},"'src\u002Fentities'",[112,16614,179],{"class":129},[112,16616,16617],{"class":114,"line":1302},[112,16618,376],{"class":129},[112,16620,16621],{"class":114,"line":1502},[112,16622,1452],{"class":129},[83,16624,16625],{"id":16625},"マイグレーションの実行",[103,16627,16629],{"className":771,"code":16628,"language":773,"meta":108,"style":108},"# ビルド\nnpm run build\n\n# マイグレーションファイルを生成\nnpx ts-node .\u002Fnode_modules\u002F.bin\u002Ftypeorm migration:generate --name user\n\n# マイグレーションを実行\nnpx ts-node .\u002Fnode_modules\u002F.bin\u002Ftypeorm migration:run\n",[90,16630,16631,16636,16645,16649,16654,16674,16678,16683],{"__ignoreMap":108},[112,16632,16633],{"class":114,"line":115},[112,16634,16635],{"class":118},"# ビルド\n",[112,16637,16638,16640,16642],{"class":114,"line":122},[112,16639,6832],{"class":163},[112,16641,6901],{"class":136},[112,16643,16644],{"class":136}," build\n",[112,16646,16647],{"class":114,"line":143},[112,16648,147],{"emptyLinePlaceholder":146},[112,16650,16651],{"class":114,"line":150},[112,16652,16653],{"class":118},"# マイグレーションファイルを生成\n",[112,16655,16656,16659,16662,16665,16668,16671],{"class":114,"line":170},[112,16657,16658],{"class":163},"npx",[112,16660,16661],{"class":136}," ts-node",[112,16663,16664],{"class":136}," .\u002Fnode_modules\u002F.bin\u002Ftypeorm",[112,16666,16667],{"class":136}," migration:generate",[112,16669,16670],{"class":156}," --name",[112,16672,16673],{"class":136}," user\n",[112,16675,16676],{"class":114,"line":182},[112,16677,147],{"emptyLinePlaceholder":146},[112,16679,16680],{"class":114,"line":193},[112,16681,16682],{"class":118},"# マイグレーションを実行\n",[112,16684,16685,16687,16689,16691],{"class":114,"line":205},[112,16686,16658],{"class":163},[112,16688,16661],{"class":136},[112,16690,16664],{"class":136},[112,16692,16693],{"class":136}," migration:run\n",[83,16695,16697],{"id":16696},"dto定義","DTO定義",[103,16699,16701],{"className":771,"code":16700,"language":773,"meta":108,"style":108},"npm install --save class-validator class-transformer\n",[90,16702,16703],{"__ignoreMap":108},[112,16704,16705,16707,16709,16711,16713],{"class":114,"line":115},[112,16706,6832],{"class":163},[112,16708,7883],{"class":136},[112,16710,15470],{"class":156},[112,16712,11479],{"class":136},[112,16714,11482],{"class":136},[103,16716,16718],{"className":105,"code":16717,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fdto\u002Fcreate-user.dto.ts\nimport { IsEmail, IsNotEmpty, Matches, MaxLength } from 'class-validator'\n\nexport class CreateUserDto {\n  @IsNotEmpty({ message: '名前は必ず入力してください' })\n  @MaxLength(255, { message: '名前は255文字以内で入力してください' })\n  name: string\n\n  @IsNotEmpty({ message: 'Emailは必ず入力してください' })\n  @MaxLength(255, { message: 'Emailは255文字以内で入力してください' })\n  @IsEmail({}, { message: '正しいEmail形式で入力してください' })\n  email: string\n\n  @IsNotEmpty({ message: 'パスワードは必ず入力してください' })\n  @Matches(\u002F^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,25}$\u002F, {\n    message: 'パスワードは大文字小文字を含む8文字以上25文字以内で設定してください',\n  })\n  password: string\n}\n",[90,16719,16720,16725,16736,16740,16750,16765,16784,16792,16796,16809,16826,16840,16848,16852,16865,16921,16931,16935,16943],{"__ignoreMap":108},[112,16721,16722],{"class":114,"line":115},[112,16723,16724],{"class":118},"\u002F\u002F src\u002Fdto\u002Fcreate-user.dto.ts\n",[112,16726,16727,16729,16732,16734],{"class":114,"line":122},[112,16728,126],{"class":125},[112,16730,16731],{"class":129}," { IsEmail, IsNotEmpty, Matches, MaxLength } ",[112,16733,133],{"class":125},[112,16735,11366],{"class":136},[112,16737,16738],{"class":114,"line":143},[112,16739,147],{"emptyLinePlaceholder":146},[112,16741,16742,16744,16746,16748],{"class":114,"line":150},[112,16743,288],{"class":125},[112,16745,9931],{"class":125},[112,16747,10957],{"class":163},[112,16749,1294],{"class":129},[112,16751,16752,16754,16757,16760,16763],{"class":114,"line":170},[112,16753,11079],{"class":129},[112,16755,16756],{"class":163},"IsNotEmpty",[112,16758,16759],{"class":129},"({ message: ",[112,16761,16762],{"class":136},"'名前は必ず入力してください'",[112,16764,11585],{"class":129},[112,16766,16767,16769,16772,16774,16776,16779,16782],{"class":114,"line":182},[112,16768,11079],{"class":129},[112,16770,16771],{"class":163},"MaxLength",[112,16773,425],{"class":129},[112,16775,16194],{"class":156},[112,16777,16778],{"class":129},", { message: ",[112,16780,16781],{"class":136},"'名前は255文字以内で入力してください'",[112,16783,11585],{"class":129},[112,16785,16786,16788,16790],{"class":114,"line":193},[112,16787,10020],{"class":222},[112,16789,2243],{"class":125},[112,16791,10006],{"class":156},[112,16793,16794],{"class":114,"line":205},[112,16795,147],{"emptyLinePlaceholder":146},[112,16797,16798,16800,16802,16804,16807],{"class":114,"line":241},[112,16799,11079],{"class":129},[112,16801,16756],{"class":163},[112,16803,16759],{"class":129},[112,16805,16806],{"class":136},"'Emailは必ず入力してください'",[112,16808,11585],{"class":129},[112,16810,16811,16813,16815,16817,16819,16821,16824],{"class":114,"line":247},[112,16812,11079],{"class":129},[112,16814,16771],{"class":163},[112,16816,425],{"class":129},[112,16818,16194],{"class":156},[112,16820,16778],{"class":129},[112,16822,16823],{"class":136},"'Emailは255文字以内で入力してください'",[112,16825,11585],{"class":129},[112,16827,16828,16830,16832,16835,16838],{"class":114,"line":351},[112,16829,11079],{"class":129},[112,16831,11387],{"class":163},[112,16833,16834],{"class":129},"({}, { message: ",[112,16836,16837],{"class":136},"'正しいEmail形式で入力してください'",[112,16839,11585],{"class":129},[112,16841,16842,16844,16846],{"class":114,"line":361},[112,16843,10001],{"class":222},[112,16845,2243],{"class":125},[112,16847,10006],{"class":156},[112,16849,16850],{"class":114,"line":367},[112,16851,147],{"emptyLinePlaceholder":146},[112,16853,16854,16856,16858,16860,16863],{"class":114,"line":373},[112,16855,11079],{"class":129},[112,16857,16756],{"class":163},[112,16859,16759],{"class":129},[112,16861,16862],{"class":136},"'パスワードは必ず入力してください'",[112,16864,11585],{"class":129},[112,16866,16867,16869,16872,16874,16876,16879,16883,16885,16888,16891,16894,16896,16898,16901,16903,16905,16907,16910,16912,16914,16917,16919],{"class":114,"line":379},[112,16868,11079],{"class":129},[112,16870,16871],{"class":163},"Matches",[112,16873,425],{"class":129},[112,16875,6956],{"class":136},[112,16877,16878],{"class":125},"^",[112,16880,16882],{"class":16881},"sA_wV","(?=",[112,16884,2124],{"class":156},[112,16886,16887],{"class":125},"*",[112,16889,16890],{"class":156},"\\d",[112,16892,16893],{"class":16881},")(?=",[112,16895,2124],{"class":156},[112,16897,16887],{"class":125},[112,16899,16900],{"class":156},"[a-z]",[112,16902,16893],{"class":16881},[112,16904,2124],{"class":156},[112,16906,16887],{"class":125},[112,16908,16909],{"class":156},"[A-Z]",[112,16911,10186],{"class":16881},[112,16913,2124],{"class":156},[112,16915,16916],{"class":125},"{8,25}$",[112,16918,6956],{"class":136},[112,16920,5239],{"class":129},[112,16922,16923,16926,16929],{"class":114,"line":1302},[112,16924,16925],{"class":129},"    message: ",[112,16927,16928],{"class":136},"'パスワードは大文字小文字を含む8文字以上25文字以内で設定してください'",[112,16930,179],{"class":129},[112,16932,16933],{"class":114,"line":1502},[112,16934,8004],{"class":129},[112,16936,16937,16939,16941],{"class":114,"line":1507},[112,16938,10011],{"class":222},[112,16940,2243],{"class":125},[112,16942,10006],{"class":156},[112,16944,16945],{"class":114,"line":1512},[112,16946,1452],{"class":129},[83,16948,16950],{"id":16949},"service実装","Service実装",[103,16952,16954],{"className":771,"code":16953,"language":773,"meta":108,"style":108},"npm install bcrypt\nnpm install -D @types\u002Fbcrypt\n",[90,16955,16956,16965],{"__ignoreMap":108},[112,16957,16958,16960,16962],{"class":114,"line":115},[112,16959,6832],{"class":163},[112,16961,7883],{"class":136},[112,16963,16964],{"class":136}," bcrypt\n",[112,16966,16967,16969,16971,16973],{"class":114,"line":122},[112,16968,6832],{"class":163},[112,16970,7883],{"class":136},[112,16972,9666],{"class":156},[112,16974,16975],{"class":136}," @types\u002Fbcrypt\n",[103,16977,16979],{"className":105,"code":16978,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fservices\u002Fusers.service.ts\nimport { Injectable, BadRequestException } from '@nestjs\u002Fcommon'\nimport { InjectRepository } from '@nestjs\u002Ftypeorm'\nimport { Repository } from 'typeorm'\nimport { User } from '..\u002Fentities\u002Fuser.entity'\nimport { CreateUserDto } from '..\u002Fdto\u002Fcreate-user.dto'\nimport * as bcrypt from 'bcrypt'\n\n@Injectable()\nexport class UsersService {\n  constructor(\n    @InjectRepository(User)\n    private usersRepository: Repository\u003CUser>,\n  ) {}\n\n  async create(createUserDto: CreateUserDto): Promise\u003C{ message: string }> {\n    const existingUser = await this.usersRepository.findOne({\n      where: { email: createUserDto.email },\n    })\n\n    if (existingUser) {\n      throw new BadRequestException('既に登録済みのメールアドレスです')\n    }\n\n    await this.usersRepository.save({\n      name: createUserDto.name,\n      email: createUserDto.email,\n      password: await bcrypt.hash(createUserDto.password, 10),\n    })\n\n    return { message: 'ユーザーの登録に成功しました' }\n  }\n\n  async findAll(): Promise\u003CUser[]> {\n    return this.usersRepository.find()\n  }\n\n  async findOne(id: number): Promise\u003CUser> {\n    return this.usersRepository.findOneOrFail(id)\n  }\n\n  \u002F\u002F update, removeメソッドも同様に実装\n}\n",[90,16980,16981,16986,16997,17008,17019,17031,17043,17061,17065,17073,17084,17090,17100,17119,17124,17128,17161,17182,17187,17191,17195,17202,17218,17222,17226,17239,17244,17249,17269,17273,17277,17288,17292,17296,17316,17329,17333,17337,17366,17380,17384,17388,17393],{"__ignoreMap":108},[112,16982,16983],{"class":114,"line":115},[112,16984,16985],{"class":118},"\u002F\u002F src\u002Fservices\u002Fusers.service.ts\n",[112,16987,16988,16990,16993,16995],{"class":114,"line":122},[112,16989,126],{"class":125},[112,16991,16992],{"class":129}," { Injectable, BadRequestException } ",[112,16994,133],{"class":125},[112,16996,9868],{"class":136},[112,16998,16999,17001,17004,17006],{"class":114,"line":143},[112,17000,126],{"class":125},[112,17002,17003],{"class":129}," { InjectRepository } ",[112,17005,133],{"class":125},[112,17007,15712],{"class":136},[112,17009,17010,17012,17015,17017],{"class":114,"line":150},[112,17011,126],{"class":125},[112,17013,17014],{"class":129}," { Repository } ",[112,17016,133],{"class":125},[112,17018,16078],{"class":136},[112,17020,17021,17023,17026,17028],{"class":114,"line":170},[112,17022,126],{"class":125},[112,17024,17025],{"class":129}," { User } ",[112,17027,133],{"class":125},[112,17029,17030],{"class":136}," '..\u002Fentities\u002Fuser.entity'\n",[112,17032,17033,17035,17038,17040],{"class":114,"line":182},[112,17034,126],{"class":125},[112,17036,17037],{"class":129}," { CreateUserDto } ",[112,17039,133],{"class":125},[112,17041,17042],{"class":136}," '..\u002Fdto\u002Fcreate-user.dto'\n",[112,17044,17045,17047,17050,17053,17056,17058],{"class":114,"line":193},[112,17046,126],{"class":125},[112,17048,17049],{"class":156}," *",[112,17051,17052],{"class":125}," as",[112,17054,17055],{"class":129}," bcrypt ",[112,17057,133],{"class":125},[112,17059,17060],{"class":136}," 'bcrypt'\n",[112,17062,17063],{"class":114,"line":205},[112,17064,147],{"emptyLinePlaceholder":146},[112,17066,17067,17069,17071],{"class":114,"line":241},[112,17068,9901],{"class":129},[112,17070,10076],{"class":163},[112,17072,630],{"class":129},[112,17074,17075,17077,17079,17082],{"class":114,"line":247},[112,17076,288],{"class":125},[112,17078,9931],{"class":125},[112,17080,17081],{"class":163}," UsersService",[112,17083,1294],{"class":129},[112,17085,17086,17088],{"class":114,"line":351},[112,17087,10128],{"class":125},[112,17089,2304],{"class":129},[112,17091,17092,17094,17097],{"class":114,"line":361},[112,17093,11218],{"class":129},[112,17095,17096],{"class":163},"InjectRepository",[112,17098,17099],{"class":129},"(User)\n",[112,17101,17102,17104,17107,17109,17112,17114,17116],{"class":114,"line":367},[112,17103,13386],{"class":125},[112,17105,17106],{"class":222}," usersRepository",[112,17108,2243],{"class":125},[112,17110,17111],{"class":163}," Repository",[112,17113,6944],{"class":129},[112,17115,10489],{"class":163},[112,17117,17118],{"class":129},">,\n",[112,17120,17121],{"class":114,"line":373},[112,17122,17123],{"class":129},"  ) {}\n",[112,17125,17126],{"class":114,"line":379},[112,17127,147],{"emptyLinePlaceholder":146},[112,17129,17130,17132,17134,17136,17139,17141,17143,17145,17147,17149,17152,17154,17156,17158],{"class":114,"line":1302},[112,17131,10270],{"class":125},[112,17133,6835],{"class":163},[112,17135,425],{"class":129},[112,17137,17138],{"class":222},"createUserDto",[112,17140,2243],{"class":125},[112,17142,10957],{"class":163},[112,17144,10186],{"class":129},[112,17146,2243],{"class":125},[112,17148,10289],{"class":163},[112,17150,17151],{"class":129},"\u003C{ ",[112,17153,10811],{"class":222},[112,17155,2243],{"class":125},[112,17157,10478],{"class":156},[112,17159,17160],{"class":129}," }> {\n",[112,17162,17163,17165,17168,17170,17172,17174,17177,17180],{"class":114,"line":1502},[112,17164,3472],{"class":125},[112,17166,17167],{"class":156}," existingUser",[112,17169,160],{"class":125},[112,17171,419],{"class":125},[112,17173,10318],{"class":156},[112,17175,17176],{"class":129},".usersRepository.",[112,17178,17179],{"class":163},"findOne",[112,17181,167],{"class":129},[112,17183,17184],{"class":114,"line":1507},[112,17185,17186],{"class":129},"      where: { email: createUserDto.email },\n",[112,17188,17189],{"class":114,"line":1512},[112,17190,10257],{"class":129},[112,17192,17193],{"class":114,"line":1518},[112,17194,147],{"emptyLinePlaceholder":146},[112,17196,17197,17199],{"class":114,"line":1524},[112,17198,3511],{"class":125},[112,17200,17201],{"class":129}," (existingUser) {\n",[112,17203,17204,17206,17208,17211,17213,17216],{"class":114,"line":1530},[112,17205,3519],{"class":125},[112,17207,232],{"class":125},[112,17209,17210],{"class":163}," BadRequestException",[112,17212,425],{"class":129},[112,17214,17215],{"class":136},"'既に登録済みのメールアドレスです'",[112,17217,431],{"class":129},[112,17219,17220],{"class":114,"line":1536},[112,17221,3118],{"class":129},[112,17223,17224],{"class":114,"line":1542},[112,17225,147],{"emptyLinePlaceholder":146},[112,17227,17228,17230,17232,17234,17237],{"class":114,"line":2402},[112,17229,3385],{"class":125},[112,17231,10318],{"class":156},[112,17233,17176],{"class":129},[112,17235,17236],{"class":163},"save",[112,17238,167],{"class":129},[112,17240,17241],{"class":114,"line":2407},[112,17242,17243],{"class":129},"      name: createUserDto.name,\n",[112,17245,17246],{"class":114,"line":2413},[112,17247,17248],{"class":129},"      email: createUserDto.email,\n",[112,17250,17251,17254,17256,17259,17262,17265,17267],{"class":114,"line":2446},[112,17252,17253],{"class":129},"      password: ",[112,17255,5015],{"class":125},[112,17257,17258],{"class":129}," bcrypt.",[112,17260,17261],{"class":163},"hash",[112,17263,17264],{"class":129},"(createUserDto.password, ",[112,17266,12294],{"class":156},[112,17268,15842],{"class":129},[112,17270,17271],{"class":114,"line":2451},[112,17272,10257],{"class":129},[112,17274,17275],{"class":114,"line":2464},[112,17276,147],{"emptyLinePlaceholder":146},[112,17278,17279,17281,17283,17286],{"class":114,"line":2481},[112,17280,3973],{"class":125},[112,17282,11332],{"class":129},[112,17284,17285],{"class":136},"'ユーザーの登録に成功しました'",[112,17287,2395],{"class":129},[112,17289,17290],{"class":114,"line":2486},[112,17291,3232],{"class":129},[112,17293,17294],{"class":114,"line":3229},[112,17295,147],{"emptyLinePlaceholder":146},[112,17297,17298,17300,17303,17305,17307,17309,17311,17313],{"class":114,"line":3235},[112,17299,10270],{"class":125},[112,17301,17302],{"class":163}," findAll",[112,17304,13899],{"class":129},[112,17306,2243],{"class":125},[112,17308,10289],{"class":163},[112,17310,6944],{"class":129},[112,17312,10489],{"class":163},[112,17314,17315],{"class":129},"[]> {\n",[112,17317,17318,17320,17322,17324,17327],{"class":114,"line":3657},[112,17319,3973],{"class":125},[112,17321,10318],{"class":156},[112,17323,17176],{"class":129},[112,17325,17326],{"class":163},"find",[112,17328,630],{"class":129},[112,17330,17331],{"class":114,"line":3662},[112,17332,3232],{"class":129},[112,17334,17335],{"class":114,"line":3667},[112,17336,147],{"emptyLinePlaceholder":146},[112,17338,17339,17341,17344,17346,17349,17351,17354,17356,17358,17360,17362,17364],{"class":114,"line":3680},[112,17340,10270],{"class":125},[112,17342,17343],{"class":163}," findOne",[112,17345,425],{"class":129},[112,17347,17348],{"class":222},"id",[112,17350,2243],{"class":125},[112,17352,17353],{"class":156}," number",[112,17355,10186],{"class":129},[112,17357,2243],{"class":125},[112,17359,10289],{"class":163},[112,17361,6944],{"class":129},[112,17363,10489],{"class":163},[112,17365,10297],{"class":129},[112,17367,17368,17370,17372,17374,17377],{"class":114,"line":3692},[112,17369,3973],{"class":125},[112,17371,10318],{"class":156},[112,17373,17176],{"class":129},[112,17375,17376],{"class":163},"findOneOrFail",[112,17378,17379],{"class":129},"(id)\n",[112,17381,17382],{"class":114,"line":3716},[112,17383,3232],{"class":129},[112,17385,17386],{"class":114,"line":3737},[112,17387,147],{"emptyLinePlaceholder":146},[112,17389,17390],{"class":114,"line":3742},[112,17391,17392],{"class":118},"  \u002F\u002F update, removeメソッドも同様に実装\n",[112,17394,17395],{"class":114,"line":3747},[112,17396,1452],{"class":129},[11,17398,14648],{"id":17399},"e2eテストの実装",[83,17401,17403],{"id":17402},"テスト用typeorm設定","テスト用TypeORM設定",[103,17405,17407],{"className":105,"code":17406,"language":107,"meta":108,"style":108},"\u002F\u002F ormconfig.test.ts\nmodule.exports = {\n  type: 'mysql',\n  host: process.env.DB_HOST || 'localhost',\n  port: process.env.DB_PORT || '3306',\n  username: process.env.DB_USERNAME || 'root',\n  password: process.env.DB_PASSWORD || 'password',\n  database: process.env.DB_NAME || 'meetup',\n  synchronize: true, \u002F\u002F テスト環境のみ有効化\n  logging: true,\n  dropSchema: true,\n  entities: ['src\u002Fentities\u002F*.ts'],\n  migrations: ['src\u002Fdatabases\u002Fmigrations\u002F*.ts'],\n}\n",[90,17408,17409,17414,17426,17434,17446,17458,17470,17482,17494,17505,17513,17522,17530,17538],{"__ignoreMap":108},[112,17410,17411],{"class":114,"line":115},[112,17412,17413],{"class":118},"\u002F\u002F ormconfig.test.ts\n",[112,17415,17416,17418,17420,17422,17424],{"class":114,"line":122},[112,17417,9850],{"class":156},[112,17419,2124],{"class":129},[112,17421,16473],{"class":156},[112,17423,160],{"class":125},[112,17425,1294],{"class":129},[112,17427,17428,17430,17432],{"class":114,"line":143},[112,17429,173],{"class":129},[112,17431,15825],{"class":136},[112,17433,179],{"class":129},[112,17435,17436,17438,17440,17442,17444],{"class":114,"line":150},[112,17437,16490],{"class":129},[112,17439,15586],{"class":156},[112,17441,10852],{"class":125},[112,17443,15591],{"class":136},[112,17445,179],{"class":129},[112,17447,17448,17450,17452,17454,17456],{"class":114,"line":170},[112,17449,16503],{"class":129},[112,17451,15604],{"class":156},[112,17453,10852],{"class":125},[112,17455,16510],{"class":136},[112,17457,179],{"class":129},[112,17459,17460,17462,17464,17466,17468],{"class":114,"line":182},[112,17461,16517],{"class":129},[112,17463,15621],{"class":156},[112,17465,10852],{"class":125},[112,17467,15626],{"class":136},[112,17469,179],{"class":129},[112,17471,17472,17474,17476,17478,17480],{"class":114,"line":193},[112,17473,16530],{"class":129},[112,17475,15636],{"class":156},[112,17477,10852],{"class":125},[112,17479,15641],{"class":136},[112,17481,179],{"class":129},[112,17483,17484,17486,17488,17490,17492],{"class":114,"line":205},[112,17485,16543],{"class":129},[112,17487,15651],{"class":156},[112,17489,10852],{"class":125},[112,17491,15656],{"class":136},[112,17493,179],{"class":129},[112,17495,17496,17498,17500,17502],{"class":114,"line":241},[112,17497,16556],{"class":129},[112,17499,4345],{"class":156},[112,17501,447],{"class":129},[112,17503,17504],{"class":118},"\u002F\u002F テスト環境のみ有効化\n",[112,17506,17507,17509,17511],{"class":114,"line":247},[112,17508,16565],{"class":129},[112,17510,4345],{"class":156},[112,17512,179],{"class":129},[112,17514,17515,17518,17520],{"class":114,"line":351},[112,17516,17517],{"class":129},"  dropSchema: ",[112,17519,4345],{"class":156},[112,17521,179],{"class":129},[112,17523,17524,17526,17528],{"class":114,"line":361},[112,17525,16574],{"class":129},[112,17527,16577],{"class":136},[112,17529,746],{"class":129},[112,17531,17532,17534,17536],{"class":114,"line":367},[112,17533,16584],{"class":129},[112,17535,16587],{"class":136},[112,17537,746],{"class":129},[112,17539,17540],{"class":114,"line":373},[112,17541,1452],{"class":129},[83,17543,17545],{"id":17544},"e2eテストコード","E2Eテストコード",[103,17547,17549],{"className":771,"code":17548,"language":773,"meta":108,"style":108},"npm install randomstring typeorm-seeding\n",[90,17550,17551],{"__ignoreMap":108},[112,17552,17553,17555,17557,17560],{"class":114,"line":115},[112,17554,6832],{"class":163},[112,17556,7883],{"class":136},[112,17558,17559],{"class":136}," randomstring",[112,17561,17562],{"class":136}," typeorm-seeding\n",[103,17564,17566],{"className":105,"code":17565,"language":107,"meta":108,"style":108},"\u002F\u002F test\u002Fuser.e2e-spec.ts\nimport { INestApplication, ValidationPipe } from '@nestjs\u002Fcommon'\nimport { Test, TestingModule } from '@nestjs\u002Ftesting'\nimport { TypeOrmModule } from '@nestjs\u002Ftypeorm'\nimport * as request from 'supertest'\nimport { useRefreshDatabase } from 'typeorm-seeding'\nimport { User } from '..\u002Fsrc\u002Fentities\u002Fuser.entity'\nimport { AppModule } from '..\u002Fsrc\u002Fmodules\u002Fapp.module'\nimport { CreateUserDto } from '..\u002Fsrc\u002Fdto\u002Fcreate-user.dto'\n\ndescribe('UserController (E2E)', () => {\n  let app: INestApplication\n\n  beforeEach(async () => {\n    await useRefreshDatabase()\n  })\n\n  beforeAll(async () => {\n    const moduleFixture: TestingModule = await Test.createTestingModule({\n      imports: [TypeOrmModule.forFeature([User]), AppModule],\n    }).compile()\n\n    app = moduleFixture.createNestApplication()\n    app.useGlobalPipes(new ValidationPipe())\n    await app.init()\n  })\n\n  afterAll(async () => {\n    await app.close()\n  })\n\n  describe('ユーザー登録テスト', () => {\n    it('正常にユーザーを登録できる', async () => {\n      const body: CreateUserDto = {\n        name: 'Test User',\n        email: 'test@example.com',\n        password: 'Password1234',\n      }\n\n      const res = await request(app.getHttpServer())\n        .post('\u002Fusers')\n        .set('Accept', 'application\u002Fjson')\n        .send(body)\n\n      expect(res.status).toEqual(201)\n      expect(res.body.message).toEqual('ユーザーの登録に成功しました')\n    })\n\n    it('重複メールアドレスでエラーになる', async () => {\n      const body: CreateUserDto = {\n        name: 'Test User',\n        email: 'test@example.com',\n        password: 'Password1234',\n      }\n\n      await request(app.getHttpServer()).post('\u002Fusers').send(body)\n\n      const res = await request(app.getHttpServer()).post('\u002Fusers').send(body)\n\n      expect(res.status).toEqual(400)\n    })\n  })\n})\n",[90,17567,17568,17573,17584,17594,17604,17620,17632,17643,17654,17665,17669,17684,17695,17699,17713,17722,17726,17730,17745,17766,17777,17785,17789,17804,17822,17832,17836,17840,17855,17866,17870,17874,17890,17910,17925,17935,17945,17955,17959,17963,17984,17998,18015,18025,18029,18047,18062,18066,18070,18089,18103,18111,18119,18127,18131,18135,18159,18163,18193,18197,18212,18216,18220],{"__ignoreMap":108},[112,17569,17570],{"class":114,"line":115},[112,17571,17572],{"class":118},"\u002F\u002F test\u002Fuser.e2e-spec.ts\n",[112,17574,17575,17577,17580,17582],{"class":114,"line":122},[112,17576,126],{"class":125},[112,17578,17579],{"class":129}," { INestApplication, ValidationPipe } ",[112,17581,133],{"class":125},[112,17583,9868],{"class":136},[112,17585,17586,17588,17590,17592],{"class":114,"line":143},[112,17587,126],{"class":125},[112,17589,12367],{"class":129},[112,17591,133],{"class":125},[112,17593,12372],{"class":136},[112,17595,17596,17598,17600,17602],{"class":114,"line":150},[112,17597,126],{"class":125},[112,17599,15707],{"class":129},[112,17601,133],{"class":125},[112,17603,15712],{"class":136},[112,17605,17606,17608,17610,17612,17615,17617],{"class":114,"line":170},[112,17607,126],{"class":125},[112,17609,17049],{"class":156},[112,17611,17052],{"class":125},[112,17613,17614],{"class":129}," request ",[112,17616,133],{"class":125},[112,17618,17619],{"class":136}," 'supertest'\n",[112,17621,17622,17624,17627,17629],{"class":114,"line":182},[112,17623,126],{"class":125},[112,17625,17626],{"class":129}," { useRefreshDatabase } ",[112,17628,133],{"class":125},[112,17630,17631],{"class":136}," 'typeorm-seeding'\n",[112,17633,17634,17636,17638,17640],{"class":114,"line":193},[112,17635,126],{"class":125},[112,17637,17025],{"class":129},[112,17639,133],{"class":125},[112,17641,17642],{"class":136}," '..\u002Fsrc\u002Fentities\u002Fuser.entity'\n",[112,17644,17645,17647,17649,17651],{"class":114,"line":205},[112,17646,126],{"class":125},[112,17648,14010],{"class":129},[112,17650,133],{"class":125},[112,17652,17653],{"class":136}," '..\u002Fsrc\u002Fmodules\u002Fapp.module'\n",[112,17655,17656,17658,17660,17662],{"class":114,"line":241},[112,17657,126],{"class":125},[112,17659,17037],{"class":129},[112,17661,133],{"class":125},[112,17663,17664],{"class":136}," '..\u002Fsrc\u002Fdto\u002Fcreate-user.dto'\n",[112,17666,17667],{"class":114,"line":247},[112,17668,147],{"emptyLinePlaceholder":146},[112,17670,17671,17673,17675,17678,17680,17682],{"class":114,"line":351},[112,17672,12401],{"class":163},[112,17674,425],{"class":129},[112,17676,17677],{"class":136},"'UserController (E2E)'",[112,17679,595],{"class":129},[112,17681,229],{"class":125},[112,17683,1294],{"class":129},[112,17685,17686,17688,17690,17692],{"class":114,"line":361},[112,17687,12417],{"class":125},[112,17689,14060],{"class":129},[112,17691,2243],{"class":125},[112,17693,17694],{"class":163}," INestApplication\n",[112,17696,17697],{"class":114,"line":367},[112,17698,147],{"emptyLinePlaceholder":146},[112,17700,17701,17703,17705,17707,17709,17711],{"class":114,"line":373},[112,17702,12538],{"class":163},[112,17704,425],{"class":129},[112,17706,3305],{"class":125},[112,17708,3802],{"class":129},[112,17710,229],{"class":125},[112,17712,1294],{"class":129},[112,17714,17715,17717,17720],{"class":114,"line":379},[112,17716,3385],{"class":125},[112,17718,17719],{"class":163}," useRefreshDatabase",[112,17721,630],{"class":129},[112,17723,17724],{"class":114,"line":1302},[112,17725,8004],{"class":129},[112,17727,17728],{"class":114,"line":1502},[112,17729,147],{"emptyLinePlaceholder":146},[112,17731,17732,17735,17737,17739,17741,17743],{"class":114,"line":1507},[112,17733,17734],{"class":163},"  beforeAll",[112,17736,425],{"class":129},[112,17738,3305],{"class":125},[112,17740,3802],{"class":129},[112,17742,229],{"class":125},[112,17744,1294],{"class":129},[112,17746,17747,17749,17752,17754,17756,17758,17760,17762,17764],{"class":114,"line":1512},[112,17748,3472],{"class":125},[112,17750,17751],{"class":156}," moduleFixture",[112,17753,2243],{"class":125},[112,17755,12560],{"class":163},[112,17757,160],{"class":125},[112,17759,419],{"class":125},[112,17761,12567],{"class":129},[112,17763,12570],{"class":163},[112,17765,167],{"class":129},[112,17767,17768,17771,17774],{"class":114,"line":1518},[112,17769,17770],{"class":129},"      imports: [TypeOrmModule.",[112,17772,17773],{"class":163},"forFeature",[112,17775,17776],{"class":129},"([User]), AppModule],\n",[112,17778,17779,17781,17783],{"class":114,"line":1524},[112,17780,12611],{"class":129},[112,17782,12614],{"class":163},[112,17784,630],{"class":129},[112,17786,17787],{"class":114,"line":1530},[112,17788,147],{"emptyLinePlaceholder":146},[112,17790,17791,17794,17796,17799,17802],{"class":114,"line":1536},[112,17792,17793],{"class":129},"    app ",[112,17795,576],{"class":125},[112,17797,17798],{"class":129}," moduleFixture.",[112,17800,17801],{"class":163},"createNestApplication",[112,17803,630],{"class":129},[112,17805,17806,17809,17812,17814,17816,17819],{"class":114,"line":1542},[112,17807,17808],{"class":129},"    app.",[112,17810,17811],{"class":163},"useGlobalPipes",[112,17813,425],{"class":129},[112,17815,14155],{"class":125},[112,17817,17818],{"class":163}," ValidationPipe",[112,17820,17821],{"class":129},"())\n",[112,17823,17824,17826,17828,17830],{"class":114,"line":2402},[112,17825,3385],{"class":125},[112,17827,14109],{"class":129},[112,17829,4115],{"class":163},[112,17831,630],{"class":129},[112,17833,17834],{"class":114,"line":2407},[112,17835,8004],{"class":129},[112,17837,17838],{"class":114,"line":2413},[112,17839,147],{"emptyLinePlaceholder":146},[112,17841,17842,17845,17847,17849,17851,17853],{"class":114,"line":2446},[112,17843,17844],{"class":163},"  afterAll",[112,17846,425],{"class":129},[112,17848,3305],{"class":125},[112,17850,3802],{"class":129},[112,17852,229],{"class":125},[112,17854,1294],{"class":129},[112,17856,17857,17859,17861,17864],{"class":114,"line":2451},[112,17858,3385],{"class":125},[112,17860,14109],{"class":129},[112,17862,17863],{"class":163},"close",[112,17865,630],{"class":129},[112,17867,17868],{"class":114,"line":2464},[112,17869,8004],{"class":129},[112,17871,17872],{"class":114,"line":2481},[112,17873,147],{"emptyLinePlaceholder":146},[112,17875,17876,17879,17881,17884,17886,17888],{"class":114,"line":2486},[112,17877,17878],{"class":163},"  describe",[112,17880,425],{"class":129},[112,17882,17883],{"class":136},"'ユーザー登録テスト'",[112,17885,595],{"class":129},[112,17887,229],{"class":125},[112,17889,1294],{"class":129},[112,17891,17892,17895,17897,17900,17902,17904,17906,17908],{"class":114,"line":3229},[112,17893,17894],{"class":163},"    it",[112,17896,425],{"class":129},[112,17898,17899],{"class":136},"'正常にユーザーを登録できる'",[112,17901,447],{"class":129},[112,17903,3305],{"class":125},[112,17905,3802],{"class":129},[112,17907,229],{"class":125},[112,17909,1294],{"class":129},[112,17911,17912,17914,17917,17919,17921,17923],{"class":114,"line":3235},[112,17913,3575],{"class":125},[112,17915,17916],{"class":156}," body",[112,17918,2243],{"class":125},[112,17920,10957],{"class":163},[112,17922,160],{"class":125},[112,17924,1294],{"class":129},[112,17926,17927,17930,17933],{"class":114,"line":3657},[112,17928,17929],{"class":129},"        name: ",[112,17931,17932],{"class":136},"'Test User'",[112,17934,179],{"class":129},[112,17936,17937,17940,17943],{"class":114,"line":3662},[112,17938,17939],{"class":129},"        email: ",[112,17941,17942],{"class":136},"'test@example.com'",[112,17944,179],{"class":129},[112,17946,17947,17950,17953],{"class":114,"line":3667},[112,17948,17949],{"class":129},"        password: ",[112,17951,17952],{"class":136},"'Password1234'",[112,17954,179],{"class":129},[112,17956,17957],{"class":114,"line":3680},[112,17958,3643],{"class":129},[112,17960,17961],{"class":114,"line":3692},[112,17962,147],{"emptyLinePlaceholder":146},[112,17964,17965,17967,17970,17972,17974,17977,17979,17982],{"class":114,"line":3716},[112,17966,3575],{"class":125},[112,17968,17969],{"class":156}," res",[112,17971,160],{"class":125},[112,17973,419],{"class":125},[112,17975,17976],{"class":163}," request",[112,17978,14088],{"class":129},[112,17980,17981],{"class":163},"getHttpServer",[112,17983,17821],{"class":129},[112,17985,17986,17989,17991,17993,17996],{"class":114,"line":3737},[112,17987,17988],{"class":129},"        .",[112,17990,570],{"class":163},[112,17992,425],{"class":129},[112,17994,17995],{"class":136},"'\u002Fusers'",[112,17997,431],{"class":129},[112,17999,18000,18002,18004,18006,18009,18011,18013],{"class":114,"line":3742},[112,18001,17988],{"class":129},[112,18003,3842],{"class":163},[112,18005,425],{"class":129},[112,18007,18008],{"class":136},"'Accept'",[112,18010,447],{"class":129},[112,18012,5264],{"class":136},[112,18014,431],{"class":129},[112,18016,18017,18019,18022],{"class":114,"line":3747},[112,18018,17988],{"class":129},[112,18020,18021],{"class":163},"send",[112,18023,18024],{"class":129},"(body)\n",[112,18026,18027],{"class":114,"line":3752},[112,18028,147],{"emptyLinePlaceholder":146},[112,18030,18031,18034,18037,18040,18042,18045],{"class":114,"line":3758},[112,18032,18033],{"class":163},"      expect",[112,18035,18036],{"class":129},"(res.status).",[112,18038,18039],{"class":163},"toEqual",[112,18041,425],{"class":129},[112,18043,18044],{"class":156},"201",[112,18046,431],{"class":129},[112,18048,18049,18051,18054,18056,18058,18060],{"class":114,"line":3778},[112,18050,18033],{"class":163},[112,18052,18053],{"class":129},"(res.body.message).",[112,18055,18039],{"class":163},[112,18057,425],{"class":129},[112,18059,17285],{"class":136},[112,18061,431],{"class":129},[112,18063,18064],{"class":114,"line":3787},[112,18065,10257],{"class":129},[112,18067,18068],{"class":114,"line":3809},[112,18069,147],{"emptyLinePlaceholder":146},[112,18071,18072,18074,18076,18079,18081,18083,18085,18087],{"class":114,"line":3827},[112,18073,17894],{"class":163},[112,18075,425],{"class":129},[112,18077,18078],{"class":136},"'重複メールアドレスでエラーになる'",[112,18080,447],{"class":129},[112,18082,3305],{"class":125},[112,18084,3802],{"class":129},[112,18086,229],{"class":125},[112,18088,1294],{"class":129},[112,18090,18091,18093,18095,18097,18099,18101],{"class":114,"line":3834},[112,18092,3575],{"class":125},[112,18094,17916],{"class":156},[112,18096,2243],{"class":125},[112,18098,10957],{"class":163},[112,18100,160],{"class":125},[112,18102,1294],{"class":129},[112,18104,18105,18107,18109],{"class":114,"line":3848},[112,18106,17929],{"class":129},[112,18108,17932],{"class":136},[112,18110,179],{"class":129},[112,18112,18113,18115,18117],{"class":114,"line":3858},[112,18114,17939],{"class":129},[112,18116,17942],{"class":136},[112,18118,179],{"class":129},[112,18120,18121,18123,18125],{"class":114,"line":3863},[112,18122,17949],{"class":129},[112,18124,17952],{"class":136},[112,18126,179],{"class":129},[112,18128,18129],{"class":114,"line":3874},[112,18130,3643],{"class":129},[112,18132,18133],{"class":114,"line":3879},[112,18134,147],{"emptyLinePlaceholder":146},[112,18136,18137,18139,18141,18143,18145,18147,18149,18151,18153,18155,18157],{"class":114,"line":3884},[112,18138,3837],{"class":125},[112,18140,17976],{"class":163},[112,18142,14088],{"class":129},[112,18144,17981],{"class":163},[112,18146,1261],{"class":129},[112,18148,570],{"class":163},[112,18150,425],{"class":129},[112,18152,17995],{"class":136},[112,18154,610],{"class":129},[112,18156,18021],{"class":163},[112,18158,18024],{"class":129},[112,18160,18161],{"class":114,"line":3907},[112,18162,147],{"emptyLinePlaceholder":146},[112,18164,18165,18167,18169,18171,18173,18175,18177,18179,18181,18183,18185,18187,18189,18191],{"class":114,"line":3922},[112,18166,3575],{"class":125},[112,18168,17969],{"class":156},[112,18170,160],{"class":125},[112,18172,419],{"class":125},[112,18174,17976],{"class":163},[112,18176,14088],{"class":129},[112,18178,17981],{"class":163},[112,18180,1261],{"class":129},[112,18182,570],{"class":163},[112,18184,425],{"class":129},[112,18186,17995],{"class":136},[112,18188,610],{"class":129},[112,18190,18021],{"class":163},[112,18192,18024],{"class":129},[112,18194,18195],{"class":114,"line":3935},[112,18196,147],{"emptyLinePlaceholder":146},[112,18198,18199,18201,18203,18205,18207,18210],{"class":114,"line":3940},[112,18200,18033],{"class":163},[112,18202,18036],{"class":129},[112,18204,18039],{"class":163},[112,18206,425],{"class":129},[112,18208,18209],{"class":156},"400",[112,18211,431],{"class":129},[112,18213,18214],{"class":114,"line":3954},[112,18215,10257],{"class":129},[112,18217,18218],{"class":114,"line":3970},[112,18219,8004],{"class":129},[112,18221,18222],{"class":114,"line":3978},[112,18223,8436],{"class":129},[83,18225,18226],{"id":18226},"テストスクリプトの設定",[103,18228,18230],{"className":2956,"code":18229,"language":2958,"meta":108,"style":108},"\u002F\u002F package.json\n{\n  \"scripts\": {\n    \"test:e2e\": \"jest --runInBand --forceExit --detectOpenHandles --config .\u002Ftest\u002Fjest-e2e.json\"\n  }\n}\n",[90,18231,18232,18237,18241,18248,18258,18262],{"__ignoreMap":108},[112,18233,18234],{"class":114,"line":115},[112,18235,18236],{"class":118},"\u002F\u002F package.json\n",[112,18238,18239],{"class":114,"line":122},[112,18240,2965],{"class":129},[112,18242,18243,18246],{"class":114,"line":143},[112,18244,18245],{"class":156},"  \"scripts\"",[112,18247,852],{"class":129},[112,18249,18250,18253,18255],{"class":114,"line":150},[112,18251,18252],{"class":156},"    \"test:e2e\"",[112,18254,567],{"class":129},[112,18256,18257],{"class":136},"\"jest --runInBand --forceExit --detectOpenHandles --config .\u002Ftest\u002Fjest-e2e.json\"\n",[112,18259,18260],{"class":114,"line":170},[112,18261,3232],{"class":129},[112,18263,18264],{"class":114,"line":182},[112,18265,1452],{"class":129},[15,18267,18268],{},"テスト実行：",[103,18270,18272],{"className":771,"code":18271,"language":773,"meta":108,"style":108},"npm run test:e2e\n",[90,18273,18274],{"__ignoreMap":108},[112,18275,18276,18278,18280],{"class":114,"line":115},[112,18277,6832],{"class":163},[112,18279,6901],{"class":136},[112,18281,18282],{"class":136}," test:e2e\n",[11,18284,18286],{"id":18285},"github-actionsの設定","GitHub Actionsの設定",[83,18288,18290],{"id":18289},"テスト用dockerfile","テスト用Dockerfile",[103,18292,18296],{"className":18293,"code":18294,"language":18295,"meta":108,"style":108},"language-dockerfile shiki shiki-themes github-light github-dark","# DockerfileTest\nFROM node:14.16.1-alpine as build-stage\n\nWORKDIR \u002Fwork\n\nCOPY . \u002Fwork\u002F\n\nRUN npm install\n\nCMD [\"npm\",\"run\",\"test:e2e\"]\n","dockerfile",[90,18297,18298,18303,18308,18312,18317,18321,18326,18330,18335,18339],{"__ignoreMap":108},[112,18299,18300],{"class":114,"line":115},[112,18301,18302],{},"# DockerfileTest\n",[112,18304,18305],{"class":114,"line":122},[112,18306,18307],{},"FROM node:14.16.1-alpine as build-stage\n",[112,18309,18310],{"class":114,"line":143},[112,18311,147],{"emptyLinePlaceholder":146},[112,18313,18314],{"class":114,"line":150},[112,18315,18316],{},"WORKDIR \u002Fwork\n",[112,18318,18319],{"class":114,"line":170},[112,18320,147],{"emptyLinePlaceholder":146},[112,18322,18323],{"class":114,"line":182},[112,18324,18325],{},"COPY . \u002Fwork\u002F\n",[112,18327,18328],{"class":114,"line":193},[112,18329,147],{"emptyLinePlaceholder":146},[112,18331,18332],{"class":114,"line":205},[112,18333,18334],{},"RUN npm install\n",[112,18336,18337],{"class":114,"line":241},[112,18338,147],{"emptyLinePlaceholder":146},[112,18340,18341],{"class":114,"line":247},[112,18342,18343],{},"CMD [\"npm\",\"run\",\"test:e2e\"]\n",[83,18345,18346],{"id":18346},"テスト用docker-compose",[103,18348,18350],{"className":9132,"code":18349,"language":9134,"meta":108,"style":108},"# unit-test.yml\nversion: '3'\n\nservices:\n  app:\n    build:\n      context: \".\"\n      dockerfile: \"DockerfileTest\"\n    container_name: github-actions-api-test\n    ports:\n      - '3000:3000'\n    environment:\n      PORT: 3000\n      TZ: 'Asia\u002FTokyo'\n      DB_HOST: 'testdb'\n      DB_PORT: '3306'\n      DB_USERNAME: 'root'\n      DB_PASSWORD: 'password'\n      DB_NAME: 'meetup'\n    depends_on:\n      - testdb\n\n  testdb:\n    image: mysql:8.0\n    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci\n    container_name: db_container_e2e_test\n    ports:\n      - 3306:3306\n    environment:\n      TZ: 'Asia\u002FTokyo'\n      MYSQL_ROOT_PASSWORD: password\n      MYSQL_DATABASE: meetup\n",[90,18351,18352,18357,18365,18369,18375,18382,18389,18399,18409,18418,18424,18431,18437,18447,18455,18465,18475,18485,18495,18505,18511,18518,18522,18529,18537,18545,18554,18560,18567,18573,18581,18589],{"__ignoreMap":108},[112,18353,18354],{"class":114,"line":115},[112,18355,18356],{"class":118},"# unit-test.yml\n",[112,18358,18359,18361,18363],{"class":114,"line":122},[112,18360,9142],{"class":9141},[112,18362,567],{"class":129},[112,18364,15224],{"class":136},[112,18366,18367],{"class":114,"line":143},[112,18368,147],{"emptyLinePlaceholder":146},[112,18370,18371,18373],{"class":114,"line":150},[112,18372,9156],{"class":9141},[112,18374,5004],{"class":129},[112,18376,18377,18380],{"class":114,"line":170},[112,18378,18379],{"class":9141},"  app",[112,18381,5004],{"class":129},[112,18383,18384,18387],{"class":114,"line":182},[112,18385,18386],{"class":9141},"    build",[112,18388,5004],{"class":129},[112,18390,18391,18394,18396],{"class":114,"line":193},[112,18392,18393],{"class":9141},"      context",[112,18395,567],{"class":129},[112,18397,18398],{"class":136},"\".\"\n",[112,18400,18401,18404,18406],{"class":114,"line":205},[112,18402,18403],{"class":9141},"      dockerfile",[112,18405,567],{"class":129},[112,18407,18408],{"class":136},"\"DockerfileTest\"\n",[112,18410,18411,18413,18415],{"class":114,"line":241},[112,18412,15264],{"class":9141},[112,18414,567],{"class":129},[112,18416,18417],{"class":136},"github-actions-api-test\n",[112,18419,18420,18422],{"class":114,"line":247},[112,18421,12979],{"class":9141},[112,18423,5004],{"class":129},[112,18425,18426,18428],{"class":114,"line":351},[112,18427,9204],{"class":129},[112,18429,18430],{"class":136},"'3000:3000'\n",[112,18432,18433,18435],{"class":114,"line":361},[112,18434,9180],{"class":9141},[112,18436,5004],{"class":129},[112,18438,18439,18442,18444],{"class":114,"line":367},[112,18440,18441],{"class":9141},"      PORT",[112,18443,567],{"class":129},[112,18445,18446],{"class":156},"3000\n",[112,18448,18449,18451,18453],{"class":114,"line":373},[112,18450,15306],{"class":9141},[112,18452,567],{"class":129},[112,18454,15311],{"class":136},[112,18456,18457,18460,18462],{"class":114,"line":379},[112,18458,18459],{"class":9141},"      DB_HOST",[112,18461,567],{"class":129},[112,18463,18464],{"class":136},"'testdb'\n",[112,18466,18467,18470,18472],{"class":114,"line":1302},[112,18468,18469],{"class":9141},"      DB_PORT",[112,18471,567],{"class":129},[112,18473,18474],{"class":136},"'3306'\n",[112,18476,18477,18480,18482],{"class":114,"line":1502},[112,18478,18479],{"class":9141},"      DB_USERNAME",[112,18481,567],{"class":129},[112,18483,18484],{"class":136},"'root'\n",[112,18486,18487,18490,18492],{"class":114,"line":1507},[112,18488,18489],{"class":9141},"      DB_PASSWORD",[112,18491,567],{"class":129},[112,18493,18494],{"class":136},"'password'\n",[112,18496,18497,18500,18502],{"class":114,"line":1512},[112,18498,18499],{"class":9141},"      DB_NAME",[112,18501,567],{"class":129},[112,18503,18504],{"class":136},"'meetup'\n",[112,18506,18507,18509],{"class":114,"line":1518},[112,18508,12993],{"class":9141},[112,18510,5004],{"class":129},[112,18512,18513,18515],{"class":114,"line":1524},[112,18514,9204],{"class":129},[112,18516,18517],{"class":136},"testdb\n",[112,18519,18520],{"class":114,"line":1530},[112,18521,147],{"emptyLinePlaceholder":146},[112,18523,18524,18527],{"class":114,"line":1536},[112,18525,18526],{"class":9141},"  testdb",[112,18528,5004],{"class":129},[112,18530,18531,18533,18535],{"class":114,"line":1542},[112,18532,9170],{"class":9141},[112,18534,567],{"class":129},[112,18536,15250],{"class":136},[112,18538,18539,18541,18543],{"class":114,"line":2402},[112,18540,9219],{"class":9141},[112,18542,567],{"class":129},[112,18544,15259],{"class":136},[112,18546,18547,18549,18551],{"class":114,"line":2407},[112,18548,15264],{"class":9141},[112,18550,567],{"class":129},[112,18552,18553],{"class":136},"db_container_e2e_test\n",[112,18555,18556,18558],{"class":114,"line":2413},[112,18557,12979],{"class":9141},[112,18559,5004],{"class":129},[112,18561,18562,18564],{"class":114,"line":2446},[112,18563,9204],{"class":129},[112,18565,18566],{"class":136},"3306:3306\n",[112,18568,18569,18571],{"class":114,"line":2451},[112,18570,9180],{"class":9141},[112,18572,5004],{"class":129},[112,18574,18575,18577,18579],{"class":114,"line":2464},[112,18576,15306],{"class":9141},[112,18578,567],{"class":129},[112,18580,15311],{"class":136},[112,18582,18583,18585,18587],{"class":114,"line":2481},[112,18584,15316],{"class":9141},[112,18586,567],{"class":129},[112,18588,9192],{"class":136},[112,18590,18591,18593,18595],{"class":114,"line":2486},[112,18592,15325],{"class":9141},[112,18594,567],{"class":129},[112,18596,15330],{"class":136},[83,18598,18600],{"id":18599},"github-actionsワークフロー","GitHub Actionsワークフロー",[103,18602,18604],{"className":9132,"code":18603,"language":9134,"meta":108,"style":108},"# .github\u002Fworkflows\u002Frun_test.yml\nname: Run E2E Tests\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  run-test:\n    name: Run E2E Tests\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions\u002Fcheckout@v2\n\n      - name: Run tests with docker-compose\n        run: |\n          docker-compose -f .\u002Funit-test.yml build\n          docker-compose -f .\u002Funit-test.yml up --abort-on-container-exit\n        working-directory: .\u002F\n",[90,18605,18606,18611,18620,18624,18631,18638,18645,18652,18656,18663,18670,18679,18689,18693,18700,18711,18721,18725,18736,18746,18751,18756],{"__ignoreMap":108},[112,18607,18608],{"class":114,"line":115},[112,18609,18610],{"class":118},"# .github\u002Fworkflows\u002Frun_test.yml\n",[112,18612,18613,18615,18617],{"class":114,"line":122},[112,18614,16383],{"class":9141},[112,18616,567],{"class":129},[112,18618,18619],{"class":136},"Run E2E Tests\n",[112,18621,18622],{"class":114,"line":143},[112,18623,147],{"emptyLinePlaceholder":146},[112,18625,18626,18629],{"class":114,"line":150},[112,18627,18628],{"class":156},"on",[112,18630,5004],{"class":129},[112,18632,18633,18636],{"class":114,"line":170},[112,18634,18635],{"class":9141},"  push",[112,18637,5004],{"class":129},[112,18639,18640,18643],{"class":114,"line":182},[112,18641,18642],{"class":9141},"    branches",[112,18644,5004],{"class":129},[112,18646,18647,18649],{"class":114,"line":193},[112,18648,9204],{"class":129},[112,18650,18651],{"class":136},"main\n",[112,18653,18654],{"class":114,"line":205},[112,18655,147],{"emptyLinePlaceholder":146},[112,18657,18658,18661],{"class":114,"line":241},[112,18659,18660],{"class":9141},"jobs",[112,18662,5004],{"class":129},[112,18664,18665,18668],{"class":114,"line":247},[112,18666,18667],{"class":9141},"  run-test",[112,18669,5004],{"class":129},[112,18671,18672,18675,18677],{"class":114,"line":351},[112,18673,18674],{"class":9141},"    name",[112,18676,567],{"class":129},[112,18678,18619],{"class":136},[112,18680,18681,18684,18686],{"class":114,"line":361},[112,18682,18683],{"class":9141},"    runs-on",[112,18685,567],{"class":129},[112,18687,18688],{"class":136},"ubuntu-latest\n",[112,18690,18691],{"class":114,"line":367},[112,18692,147],{"emptyLinePlaceholder":146},[112,18694,18695,18698],{"class":114,"line":373},[112,18696,18697],{"class":9141},"    steps",[112,18699,5004],{"class":129},[112,18701,18702,18704,18706,18708],{"class":114,"line":379},[112,18703,9204],{"class":129},[112,18705,16383],{"class":9141},[112,18707,567],{"class":129},[112,18709,18710],{"class":136},"Checkout code\n",[112,18712,18713,18716,18718],{"class":114,"line":1302},[112,18714,18715],{"class":9141},"        uses",[112,18717,567],{"class":129},[112,18719,18720],{"class":136},"actions\u002Fcheckout@v2\n",[112,18722,18723],{"class":114,"line":1502},[112,18724,147],{"emptyLinePlaceholder":146},[112,18726,18727,18729,18731,18733],{"class":114,"line":1507},[112,18728,9204],{"class":129},[112,18730,16383],{"class":9141},[112,18732,567],{"class":129},[112,18734,18735],{"class":136},"Run tests with docker-compose\n",[112,18737,18738,18741,18743],{"class":114,"line":1512},[112,18739,18740],{"class":9141},"        run",[112,18742,567],{"class":129},[112,18744,18745],{"class":125},"|\n",[112,18747,18748],{"class":114,"line":1518},[112,18749,18750],{"class":136},"          docker-compose -f .\u002Funit-test.yml build\n",[112,18752,18753],{"class":114,"line":1524},[112,18754,18755],{"class":136},"          docker-compose -f .\u002Funit-test.yml up --abort-on-container-exit\n",[112,18757,18758,18761,18763],{"class":114,"line":1530},[112,18759,18760],{"class":9141},"        working-directory",[112,18762,567],{"class":129},[112,18764,18765],{"class":136},".\u002F\n",[11,18767,919],{"id":919},[15,18769,18770],{},"NestJS + Jest + GitHub ActionsでCI環境を構築する手順を解説しました。",[15,18772,18773],{},[27,18774,18775],{},"構築したもの：",[31,18777,18778,18781,18784,18787],{},[34,18779,18780],{},"NestJSを使ったCRUDアプリケーション",[34,18782,18783],{},"TypeORMとMySQLを使ったデータベース連携",[34,18785,18786],{},"Jestを使ったE2Eテスト",[34,18788,18789],{},"GitHub Actionsによる自動テスト実行",[15,18791,18792],{},[27,18793,18794],{},"重要なポイント：",[31,18796,18797,18804,18811,18814],{},[34,18798,18799,18800,18803],{},"TypeORMの",[90,18801,18802],{},"synchronize","はテスト環境のみで有効化",[34,18805,18806,18807,18810],{},"E2Eテストでは",[90,18808,18809],{},"beforeEach","でデータベースをリセット",[34,18812,18813],{},"docker-composeで本番環境に近い構成でテスト",[34,18815,18816],{},"GitHub Actionsでプッシュごとに自動テスト実行",[15,18818,18819],{},"このCI環境により、コード変更による不具合を早期に検出できます。",[944,18821,18822],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":108,"searchDepth":122,"depth":122,"links":18824},[18825,18826,18827,18828,18829,18830,18834,18838,18843,18851,18856,18861],{"id":13,"depth":122,"text":13},{"id":14620,"depth":122,"text":14621},{"id":14634,"depth":122,"text":14634},{"id":7094,"depth":122,"text":7094},{"id":14667,"depth":122,"text":14667},{"id":14707,"depth":122,"text":14707,"children":18831},[18832,18833],{"id":14710,"depth":143,"text":14711},{"id":14823,"depth":143,"text":14824},{"id":15201,"depth":122,"text":15202,"children":18835},[18836,18837],{"id":15205,"depth":143,"text":15206},{"id":15370,"depth":143,"text":15370},{"id":15452,"depth":122,"text":15453,"children":18839},[18840,18841,18842],{"id":15456,"depth":143,"text":15456},{"id":15493,"depth":143,"text":15493},{"id":15669,"depth":143,"text":15670},{"id":15982,"depth":122,"text":15983,"children":18844},[18845,18846,18847,18848,18849,18850],{"id":15986,"depth":143,"text":15986},{"id":16017,"depth":143,"text":16018},{"id":16453,"depth":143,"text":16454},{"id":16625,"depth":143,"text":16625},{"id":16696,"depth":143,"text":16697},{"id":16949,"depth":143,"text":16950},{"id":17399,"depth":122,"text":14648,"children":18852},[18853,18854,18855],{"id":17402,"depth":143,"text":17403},{"id":17544,"depth":143,"text":17545},{"id":18226,"depth":143,"text":18226},{"id":18285,"depth":122,"text":18286,"children":18857},[18858,18859,18860],{"id":18289,"depth":143,"text":18290},{"id":18346,"depth":143,"text":18346},{"id":18599,"depth":143,"text":18600},{"id":919,"depth":122,"text":919},"2022-03-29","NestJSプロジェクトにJestを使ったE2Eテストを導入し、GitHub Actionsで自動化する手順を解説します。TypeORMとMySQLを使ったCRUDアプリケーションの実装も含む実践的なハンズオン記事です。",{"tags":18865},[18866,18867,18868,12798,18869],"githubactions","ci","jest","e2e","\u002Fblog\u002Fnestjs-ci-environment-hands-on",{"title":14610,"description":18863},"blog\u002Fnestjs-ci-environment-hands-on","u2mR_Ur0DC5r-lL7AhEcz7WKCtgYTTAXm0deFf0oPmA",{"id":18875,"title":18876,"body":18877,"date":20422,"description":20423,"extension":962,"meta":20424,"navigation":146,"path":20429,"seo":20430,"stem":20431,"__hash__":20432},"blog\u002Fblog\u002Fqase-test-management-tool.md","Qaseでテスト管理をスプレッドシートから脱却する",{"type":8,"value":18878,"toc":20385},[18879,18882,18885,18917,18921,18929,18933,18965,18969,18973,18976,18980,18983,18988,19005,19009,19012,19033,19131,19135,19138,19152,19155,19159,19162,19168,19172,19175,19180,19186,19190,19193,19198,19237,19242,19274,19278,19281,19287,19291,19294,19300,19304,19307,19312,19349,19354,19360,19364,19368,19596,19600,19728,20085,20089,20093,20096,20102,20105,20116,20120,20123,20129,20133,20136,20142,20146,20149,20155,20159,20252,20255,20259,20262,20267,20278,20282,20285,20290,20301,20305,20308,20313,20324,20326,20329,20355,20358,20360,20382],[11,18880,18881],{"id":18881},"スプレッドシートでのテスト管理の課題",[15,18883,18884],{},"多くのチームがスプレッドシートでテストケースを管理していますが、以下のような課題があります",[31,18886,18887,18893,18899,18905,18911],{},[34,18888,18889,18892],{},[27,18890,18891],{},"バージョン管理が困難",": 複数人で編集すると履歴追跡が難しい",[34,18894,18895,18898],{},[27,18896,18897],{},"テスト結果の集計が手作業",": テスト進捗の可視化に時間がかかる",[34,18900,18901,18904],{},[27,18902,18903],{},"品質のばらつき",": フォーマットが統一されず、属人化しやすい",[34,18906,18907,18910],{},[27,18908,18909],{},"自動テストとの連携が困難",": 手動テストと自動テストの結果を統合しにくい",[34,18912,18913,18916],{},[27,18914,18915],{},"検索性の低さ",": 過去のテストケースを探すのが大変",[11,18918,18920],{"id":18919},"qaseとは","Qaseとは",[15,18922,18923,18928],{},[759,18924,18927],{"href":18925,"rel":18926},"https:\u002F\u002Fqase.io\u002F",[763],"Qase","は、テストケース管理とテスト実行を一元管理できるクラウドベースのテスト管理ツールです。",[15,18930,18931],{},[27,18932,2608],{},[31,18934,18935,18941,18947,18953,18959],{},[34,18936,18937,18940],{},[27,18938,18939],{},"無料プラン",": 3ユーザーまで無料",[34,18942,18943,18946],{},[27,18944,18945],{},"手動・自動テスト対応",": 両方のテスト結果を一元管理",[34,18948,18949,18952],{},[27,18950,18951],{},"API連携",": CI\u002FCDパイプラインと統合可能",[34,18954,18955,18958],{},[27,18956,18957],{},"エクスポート機能",": テスト計画・結果をCSV\u002FPDFでエクスポート",[34,18960,18961,18964],{},[27,18962,18963],{},"テンプレート",": テストケースの品質を標準化",[11,18966,18968],{"id":18967},"qaseを選んだ理由","Qaseを選んだ理由",[83,18970,18972],{"id":18971},"_1-無料で始められる","1. 無料で始められる",[15,18974,18975],{},"3ユーザーまで無料で利用できるため、小〜中規模のチームでも導入しやすい。",[83,18977,18979],{"id":18978},"_2-テストケースの標準化","2. テストケースの標準化",[15,18981,18982],{},"入力項目が整理されており、テストケースの品質を一定に保てる。",[15,18984,18985],{},[27,18986,18987],{},"標準項目",[31,18989,18990,18993,18996,18999,19002],{},[34,18991,18992],{},"Title（タイトル）",[34,18994,18995],{},"Severity（重大度）: Trivial \u002F Minor \u002F Normal \u002F Major \u002F Critical \u002F Blocker",[34,18997,18998],{},"Priority（優先度）: Low \u002F Medium \u002F High",[34,19000,19001],{},"Type（種類）: Functional \u002F Smoke \u002F Regression \u002F Security など",[34,19003,19004],{},"Automation Status（自動化状況）: Automated \u002F To be automated \u002F Not automated",[83,19006,19008],{"id":19007},"_3-自動テストとの連携","3. 自動テストとの連携",[15,19010,19011],{},"APIを使ってCI\u002FCDパイプラインから自動的にテスト結果を反映できる。",[103,19013,19015],{"className":771,"code":19014,"language":773,"meta":108,"style":108},"# Qase Reporter for Jest\nnpm install -D jest-qase-reporter\n",[90,19016,19017,19022],{"__ignoreMap":108},[112,19018,19019],{"class":114,"line":115},[112,19020,19021],{"class":118},"# Qase Reporter for Jest\n",[112,19023,19024,19026,19028,19030],{"class":114,"line":122},[112,19025,6832],{"class":163},[112,19027,7883],{"class":136},[112,19029,9666],{"class":156},[112,19031,19032],{"class":136}," jest-qase-reporter\n",[103,19034,19036],{"className":1909,"code":19035,"language":1911,"meta":108,"style":108},"\u002F\u002F jest.config.js\nmodule.exports = {\n  reporters: [\n    'default',\n    [\n      'jest-qase-reporter',\n      {\n        apiToken: process.env.QASE_API_TOKEN,\n        projectCode: 'YOUR_PROJECT_CODE',\n        runId: process.env.QASE_RUN_ID,\n      },\n    ],\n  ],\n}\n",[90,19037,19038,19043,19055,19060,19067,19072,19079,19084,19094,19104,19114,19118,19123,19127],{"__ignoreMap":108},[112,19039,19040],{"class":114,"line":115},[112,19041,19042],{"class":118},"\u002F\u002F jest.config.js\n",[112,19044,19045,19047,19049,19051,19053],{"class":114,"line":122},[112,19046,9850],{"class":156},[112,19048,2124],{"class":129},[112,19050,16473],{"class":156},[112,19052,160],{"class":125},[112,19054,1294],{"class":129},[112,19056,19057],{"class":114,"line":143},[112,19058,19059],{"class":129},"  reporters: [\n",[112,19061,19062,19065],{"class":114,"line":150},[112,19063,19064],{"class":136},"    'default'",[112,19066,179],{"class":129},[112,19068,19069],{"class":114,"line":170},[112,19070,19071],{"class":129},"    [\n",[112,19073,19074,19077],{"class":114,"line":182},[112,19075,19076],{"class":136},"      'jest-qase-reporter'",[112,19078,179],{"class":129},[112,19080,19081],{"class":114,"line":193},[112,19082,19083],{"class":129},"      {\n",[112,19085,19086,19089,19092],{"class":114,"line":205},[112,19087,19088],{"class":129},"        apiToken: process.env.",[112,19090,19091],{"class":156},"QASE_API_TOKEN",[112,19093,179],{"class":129},[112,19095,19096,19099,19102],{"class":114,"line":241},[112,19097,19098],{"class":129},"        projectCode: ",[112,19100,19101],{"class":136},"'YOUR_PROJECT_CODE'",[112,19103,179],{"class":129},[112,19105,19106,19109,19112],{"class":114,"line":247},[112,19107,19108],{"class":129},"        runId: process.env.",[112,19110,19111],{"class":156},"QASE_RUN_ID",[112,19113,179],{"class":129},[112,19115,19116],{"class":114,"line":351},[112,19117,2373],{"class":129},[112,19119,19120],{"class":114,"line":361},[112,19121,19122],{"class":129},"    ],\n",[112,19124,19125],{"class":114,"line":367},[112,19126,1887],{"class":129},[112,19128,19129],{"class":114,"line":373},[112,19130,1452],{"class":129},[83,19132,19134],{"id":19133},"_4-テスト結果の可視化","4. テスト結果の可視化",[15,19136,19137],{},"ダッシュボードでテストの進捗状況を一目で確認できる。",[31,19139,19140,19143,19146,19149],{},[34,19141,19142],{},"全体の進捗率",[34,19144,19145],{},"Passed \u002F Failed \u002F Skipped の割合",[34,19147,19148],{},"重大度別の不具合数",[34,19150,19151],{},"テスト実行履歴",[11,19153,19154],{"id":19154},"基本的な使い方",[83,19156,19158],{"id":19157},"_1-プロジェクトの作成","1. プロジェクトの作成",[15,19160,19161],{},"新しいプロジェクトを作成します。",[103,19163,19166],{"className":19164,"code":19165,"language":5951},[5949],"Project Name: プロジェクト名（例: My Application）\nProject Code: 短縮コード（例: MA）※テストケースIDのプレフィックスになる\nDescription: プロジェクトの説明\nAccess Type: Private（推奨）\n",[90,19167,19165],{"__ignoreMap":108},[83,19169,19171],{"id":19170},"_2-テストスイートの作成","2. テストスイートの作成",[15,19173,19174],{},"テストケースをグループ化するためのスイート（フォルダのようなもの）を作成します。",[15,19176,19177],{},[27,19178,19179],{},"階層構造の例",[103,19181,19184],{"className":19182,"code":19183,"language":5951},[5949],"📁 ユーザー管理\n  📁 ログイン\n  📁 ユーザー登録\n  📁 パスワードリセット\n📁 商品管理\n  📁 商品一覧\n  📁 商品詳細\n  📁 商品検索\n",[90,19185,19183],{"__ignoreMap":108},[83,19187,19189],{"id":19188},"_3-テストケースの作成","3. テストケースの作成",[15,19191,19192],{},"テストケースを作成します。",[15,19194,19195],{},[27,19196,19197],{},"必須項目",[31,19199,19200,19206,19223],{},[34,19201,19202,19205],{},[27,19203,19204],{},"Title",": テストケースの名前",[34,19207,19208,19211,19212],{},[27,19209,19210],{},"Steps",": テスト手順\n",[4108,19213,19214,19217,19220],{},[34,19215,19216],{},"ログインページにアクセス",[34,19218,19219],{},"メールアドレスとパスワードを入力",[34,19221,19222],{},"ログインボタンをクリック",[34,19224,19225,19228,19229],{},[27,19226,19227],{},"Expected Result",": 期待される結果\n",[31,19230,19231,19234],{},[34,19232,19233],{},"ダッシュボードに遷移する",[34,19235,19236],{},"ユーザー名が表示される",[15,19238,19239],{},[27,19240,19241],{},"オプション項目",[31,19243,19244,19250,19256,19262,19268],{},[34,19245,19246,19249],{},[27,19247,19248],{},"Preconditions",": テスト実行前の条件（例: ユーザーが登録済み）",[34,19251,19252,19255],{},[27,19253,19254],{},"Severity",": 重大度",[34,19257,19258,19261],{},[27,19259,19260],{},"Priority",": 優先度",[34,19263,19264,19267],{},[27,19265,19266],{},"Type",": テストタイプ",[34,19269,19270,19273],{},[27,19271,19272],{},"Automation Status",": 自動化状況",[83,19275,19277],{"id":19276},"_4-テスト計画の作成","4. テスト計画の作成",[15,19279,19280],{},"テスト計画（Test Plan）を作成して、どのテストケースを実行するか定義します。",[103,19282,19285],{"className":19283,"code":19284,"language":5951},[5949],"Plan Name: リリース v1.2.0 テスト計画\nDescription: v1.2.0リリースに向けたテスト\nTest Cases: 対象のテストケースを選択\n",[90,19286,19284],{"__ignoreMap":108},[83,19288,19290],{"id":19289},"_5-テスト実行の作成","5. テスト実行の作成",[15,19292,19293],{},"テスト計画からテスト実行（Test Run）を作成します。",[103,19295,19298],{"className":19296,"code":19297,"language":5951},[5949],"Run Title: v1.2.0 第1回テスト実行\nEnvironment: Staging\nAssigned to: テスト担当者\n",[90,19299,19297],{"__ignoreMap":108},[83,19301,19303],{"id":19302},"_6-テストの実行と結果記録","6. テストの実行と結果記録",[15,19305,19306],{},"各テストケースを実行し、結果を記録します。",[15,19308,19309],{},[27,19310,19311],{},"結果の種類",[31,19313,19314,19321,19328,19335,19342],{},[34,19315,19316,19317,19320],{},"✅ ",[27,19318,19319],{},"Passed",": テスト成功",[34,19322,19323,19324,19327],{},"❌ ",[27,19325,19326],{},"Failed",": テスト失敗",[34,19329,19330,19331,19334],{},"⏭️ ",[27,19332,19333],{},"Skipped",": スキップ",[34,19336,19337,19338,19341],{},"🚫 ",[27,19339,19340],{},"Blocked",": ブロックされた（依存関係により実行不可）",[34,19343,19344,19345,19348],{},"⚠️ ",[27,19346,19347],{},"Invalid",": 無効（テストケース自体に問題がある）",[15,19350,19351],{},[27,19352,19353],{},"コメント例",[103,19355,19358],{"className":19356,"code":19357,"language":5951},[5949],"結果: Failed\nコメント:\n- ログインボタンをクリック後、エラーメッセージが表示される\n- エラー内容: \"Invalid credentials\"\n- 再現手順:\n  1. メールアドレス: test@example.com\n  2. パスワード: Test1234\n  3. ログインボタンをクリック\n- 添付: スクリーンショット（error.png）\n",[90,19359,19357],{"__ignoreMap":108},[11,19361,19363],{"id":19362},"cicdとの連携","CI\u002FCDとの連携",[83,19365,19367],{"id":19366},"github-actionsとの統合例","GitHub Actionsとの統合例",[103,19369,19371],{"className":9132,"code":19370,"language":9134,"meta":108,"style":108},"name: E2E Tests\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v3\n\n      - name: Setup Node\n        uses: actions\u002Fsetup-node@v3\n        with:\n          node-version: '18'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run E2E tests\n        env:\n          QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }}\n          QASE_PROJECT_CODE: MA\n          QASE_RUN_ID: ${{ github.event.number }}\n        run: npm run test:e2e\n",[90,19372,19373,19382,19386,19392,19398,19411,19418,19428,19432,19438,19445,19453,19459,19471,19475,19486,19495,19502,19512,19516,19527,19536,19540,19551,19558,19568,19578,19588],{"__ignoreMap":108},[112,19374,19375,19377,19379],{"class":114,"line":115},[112,19376,16383],{"class":9141},[112,19378,567],{"class":129},[112,19380,19381],{"class":136},"E2E Tests\n",[112,19383,19384],{"class":114,"line":122},[112,19385,147],{"emptyLinePlaceholder":146},[112,19387,19388,19390],{"class":114,"line":143},[112,19389,18628],{"class":156},[112,19391,5004],{"class":129},[112,19393,19394,19396],{"class":114,"line":150},[112,19395,18635],{"class":9141},[112,19397,5004],{"class":129},[112,19399,19400,19402,19405,19408],{"class":114,"line":170},[112,19401,18642],{"class":9141},[112,19403,19404],{"class":129},": [",[112,19406,19407],{"class":136},"main",[112,19409,19410],{"class":129},"]\n",[112,19412,19413,19416],{"class":114,"line":182},[112,19414,19415],{"class":9141},"  pull_request",[112,19417,5004],{"class":129},[112,19419,19420,19422,19424,19426],{"class":114,"line":193},[112,19421,18642],{"class":9141},[112,19423,19404],{"class":129},[112,19425,19407],{"class":136},[112,19427,19410],{"class":129},[112,19429,19430],{"class":114,"line":205},[112,19431,147],{"emptyLinePlaceholder":146},[112,19433,19434,19436],{"class":114,"line":241},[112,19435,18660],{"class":9141},[112,19437,5004],{"class":129},[112,19439,19440,19443],{"class":114,"line":247},[112,19441,19442],{"class":9141},"  test",[112,19444,5004],{"class":129},[112,19446,19447,19449,19451],{"class":114,"line":351},[112,19448,18683],{"class":9141},[112,19450,567],{"class":129},[112,19452,18688],{"class":136},[112,19454,19455,19457],{"class":114,"line":361},[112,19456,18697],{"class":9141},[112,19458,5004],{"class":129},[112,19460,19461,19463,19466,19468],{"class":114,"line":367},[112,19462,9204],{"class":129},[112,19464,19465],{"class":9141},"uses",[112,19467,567],{"class":129},[112,19469,19470],{"class":136},"actions\u002Fcheckout@v3\n",[112,19472,19473],{"class":114,"line":373},[112,19474,147],{"emptyLinePlaceholder":146},[112,19476,19477,19479,19481,19483],{"class":114,"line":379},[112,19478,9204],{"class":129},[112,19480,16383],{"class":9141},[112,19482,567],{"class":129},[112,19484,19485],{"class":136},"Setup Node\n",[112,19487,19488,19490,19492],{"class":114,"line":1302},[112,19489,18715],{"class":9141},[112,19491,567],{"class":129},[112,19493,19494],{"class":136},"actions\u002Fsetup-node@v3\n",[112,19496,19497,19500],{"class":114,"line":1502},[112,19498,19499],{"class":9141},"        with",[112,19501,5004],{"class":129},[112,19503,19504,19507,19509],{"class":114,"line":1507},[112,19505,19506],{"class":9141},"          node-version",[112,19508,567],{"class":129},[112,19510,19511],{"class":136},"'18'\n",[112,19513,19514],{"class":114,"line":1512},[112,19515,147],{"emptyLinePlaceholder":146},[112,19517,19518,19520,19522,19524],{"class":114,"line":1518},[112,19519,9204],{"class":129},[112,19521,16383],{"class":9141},[112,19523,567],{"class":129},[112,19525,19526],{"class":136},"Install dependencies\n",[112,19528,19529,19531,19533],{"class":114,"line":1524},[112,19530,18740],{"class":9141},[112,19532,567],{"class":129},[112,19534,19535],{"class":136},"npm ci\n",[112,19537,19538],{"class":114,"line":1530},[112,19539,147],{"emptyLinePlaceholder":146},[112,19541,19542,19544,19546,19548],{"class":114,"line":1536},[112,19543,9204],{"class":129},[112,19545,16383],{"class":9141},[112,19547,567],{"class":129},[112,19549,19550],{"class":136},"Run E2E tests\n",[112,19552,19553,19556],{"class":114,"line":1542},[112,19554,19555],{"class":9141},"        env",[112,19557,5004],{"class":129},[112,19559,19560,19563,19565],{"class":114,"line":2402},[112,19561,19562],{"class":9141},"          QASE_API_TOKEN",[112,19564,567],{"class":129},[112,19566,19567],{"class":136},"${{ secrets.QASE_API_TOKEN }}\n",[112,19569,19570,19573,19575],{"class":114,"line":2407},[112,19571,19572],{"class":9141},"          QASE_PROJECT_CODE",[112,19574,567],{"class":129},[112,19576,19577],{"class":136},"MA\n",[112,19579,19580,19583,19585],{"class":114,"line":2413},[112,19581,19582],{"class":9141},"          QASE_RUN_ID",[112,19584,567],{"class":129},[112,19586,19587],{"class":136},"${{ github.event.number }}\n",[112,19589,19590,19592,19594],{"class":114,"line":2446},[112,19591,18740],{"class":9141},[112,19593,567],{"class":129},[112,19595,18271],{"class":136},[83,19597,19599],{"id":19598},"playwright-with-qase","Playwright with Qase",[103,19601,19603],{"className":105,"code":19602,"language":107,"meta":108,"style":108},"\u002F\u002F playwright.config.ts\nimport { defineConfig } from '@playwright\u002Ftest'\n\nexport default defineConfig({\n  reporter: [\n    ['list'],\n    [\n      'playwright-qase-reporter',\n      {\n        apiToken: process.env.QASE_API_TOKEN,\n        projectCode: process.env.QASE_PROJECT_CODE,\n        runComplete: true,\n        basePath: 'https:\u002F\u002Fapi.qase.io\u002Fv1',\n        uploadAttachments: true,\n      },\n    ],\n  ],\n})\n",[90,19604,19605,19610,19622,19626,19636,19641,19651,19655,19662,19666,19674,19684,19693,19703,19712,19716,19720,19724],{"__ignoreMap":108},[112,19606,19607],{"class":114,"line":115},[112,19608,19609],{"class":118},"\u002F\u002F playwright.config.ts\n",[112,19611,19612,19614,19617,19619],{"class":114,"line":122},[112,19613,126],{"class":125},[112,19615,19616],{"class":129}," { defineConfig } ",[112,19618,133],{"class":125},[112,19620,19621],{"class":136}," '@playwright\u002Ftest'\n",[112,19623,19624],{"class":114,"line":143},[112,19625,147],{"emptyLinePlaceholder":146},[112,19627,19628,19630,19632,19634],{"class":114,"line":150},[112,19629,288],{"class":125},[112,19631,291],{"class":125},[112,19633,686],{"class":163},[112,19635,167],{"class":129},[112,19637,19638],{"class":114,"line":170},[112,19639,19640],{"class":129},"  reporter: [\n",[112,19642,19643,19646,19649],{"class":114,"line":182},[112,19644,19645],{"class":129},"    [",[112,19647,19648],{"class":136},"'list'",[112,19650,746],{"class":129},[112,19652,19653],{"class":114,"line":193},[112,19654,19071],{"class":129},[112,19656,19657,19660],{"class":114,"line":205},[112,19658,19659],{"class":136},"      'playwright-qase-reporter'",[112,19661,179],{"class":129},[112,19663,19664],{"class":114,"line":241},[112,19665,19083],{"class":129},[112,19667,19668,19670,19672],{"class":114,"line":247},[112,19669,19088],{"class":129},[112,19671,19091],{"class":156},[112,19673,179],{"class":129},[112,19675,19676,19679,19682],{"class":114,"line":351},[112,19677,19678],{"class":129},"        projectCode: process.env.",[112,19680,19681],{"class":156},"QASE_PROJECT_CODE",[112,19683,179],{"class":129},[112,19685,19686,19689,19691],{"class":114,"line":361},[112,19687,19688],{"class":129},"        runComplete: ",[112,19690,4345],{"class":156},[112,19692,179],{"class":129},[112,19694,19695,19698,19701],{"class":114,"line":367},[112,19696,19697],{"class":129},"        basePath: ",[112,19699,19700],{"class":136},"'https:\u002F\u002Fapi.qase.io\u002Fv1'",[112,19702,179],{"class":129},[112,19704,19705,19708,19710],{"class":114,"line":373},[112,19706,19707],{"class":129},"        uploadAttachments: ",[112,19709,4345],{"class":156},[112,19711,179],{"class":129},[112,19713,19714],{"class":114,"line":379},[112,19715,2373],{"class":129},[112,19717,19718],{"class":114,"line":1302},[112,19719,19122],{"class":129},[112,19721,19722],{"class":114,"line":1502},[112,19723,1887],{"class":129},[112,19725,19726],{"class":114,"line":1507},[112,19727,8436],{"class":129},[103,19729,19731],{"className":105,"code":19730,"language":107,"meta":108,"style":108},"\u002F\u002F tests\u002Flogin.spec.ts\nimport { test, expect } from '@playwright\u002Ftest'\nimport { qase } from 'playwright-qase-reporter'\n\ntest.describe('Login', () => {\n  qase(1, test('successful login', async ({ page }) => {\n    await page.goto('\u002Flogin')\n    await page.fill('[name=\"email\"]', 'test@example.com')\n    await page.fill('[name=\"password\"]', 'password123')\n    await page.click('[type=\"submit\"]')\n\n    await expect(page).toHaveURL('\u002Fdashboard')\n    await expect(page.locator('.user-name')).toBeVisible()\n  }))\n\n  qase(2, test('failed login with invalid credentials', async ({ page }) => {\n    await page.goto('\u002Flogin')\n    await page.fill('[name=\"email\"]', 'invalid@example.com')\n    await page.fill('[name=\"password\"]', 'wrongpassword')\n    await page.click('[type=\"submit\"]')\n\n    await expect(page.locator('.error-message')).toContainText('Invalid credentials')\n  }))\n})\n",[90,19732,19733,19738,19749,19761,19765,19783,19817,19834,19854,19874,19890,19894,19914,19939,19944,19948,19980,19994,20013,20032,20046,20050,20077,20081],{"__ignoreMap":108},[112,19734,19735],{"class":114,"line":115},[112,19736,19737],{"class":118},"\u002F\u002F tests\u002Flogin.spec.ts\n",[112,19739,19740,19742,19745,19747],{"class":114,"line":122},[112,19741,126],{"class":125},[112,19743,19744],{"class":129}," { test, expect } ",[112,19746,133],{"class":125},[112,19748,19621],{"class":136},[112,19750,19751,19753,19756,19758],{"class":114,"line":143},[112,19752,126],{"class":125},[112,19754,19755],{"class":129}," { qase } ",[112,19757,133],{"class":125},[112,19759,19760],{"class":136}," 'playwright-qase-reporter'\n",[112,19762,19763],{"class":114,"line":150},[112,19764,147],{"emptyLinePlaceholder":146},[112,19766,19767,19770,19772,19774,19777,19779,19781],{"class":114,"line":170},[112,19768,19769],{"class":129},"test.",[112,19771,12401],{"class":163},[112,19773,425],{"class":129},[112,19775,19776],{"class":136},"'Login'",[112,19778,595],{"class":129},[112,19780,229],{"class":125},[112,19782,1294],{"class":129},[112,19784,19785,19788,19790,19792,19794,19797,19799,19802,19804,19806,19808,19811,19813,19815],{"class":114,"line":182},[112,19786,19787],{"class":163},"  qase",[112,19789,425],{"class":129},[112,19791,8179],{"class":156},[112,19793,447],{"class":129},[112,19795,19796],{"class":163},"test",[112,19798,425],{"class":129},[112,19800,19801],{"class":136},"'successful login'",[112,19803,447],{"class":129},[112,19805,3305],{"class":125},[112,19807,2253],{"class":129},[112,19809,19810],{"class":222},"page",[112,19812,2259],{"class":129},[112,19814,229],{"class":125},[112,19816,1294],{"class":129},[112,19818,19819,19821,19824,19827,19829,19832],{"class":114,"line":193},[112,19820,3385],{"class":125},[112,19822,19823],{"class":129}," page.",[112,19825,19826],{"class":163},"goto",[112,19828,425],{"class":129},[112,19830,19831],{"class":136},"'\u002Flogin'",[112,19833,431],{"class":129},[112,19835,19836,19838,19840,19843,19845,19848,19850,19852],{"class":114,"line":205},[112,19837,3385],{"class":125},[112,19839,19823],{"class":129},[112,19841,19842],{"class":163},"fill",[112,19844,425],{"class":129},[112,19846,19847],{"class":136},"'[name=\"email\"]'",[112,19849,447],{"class":129},[112,19851,17942],{"class":136},[112,19853,431],{"class":129},[112,19855,19856,19858,19860,19862,19864,19867,19869,19872],{"class":114,"line":241},[112,19857,3385],{"class":125},[112,19859,19823],{"class":129},[112,19861,19842],{"class":163},[112,19863,425],{"class":129},[112,19865,19866],{"class":136},"'[name=\"password\"]'",[112,19868,447],{"class":129},[112,19870,19871],{"class":136},"'password123'",[112,19873,431],{"class":129},[112,19875,19876,19878,19880,19883,19885,19888],{"class":114,"line":247},[112,19877,3385],{"class":125},[112,19879,19823],{"class":129},[112,19881,19882],{"class":163},"click",[112,19884,425],{"class":129},[112,19886,19887],{"class":136},"'[type=\"submit\"]'",[112,19889,431],{"class":129},[112,19891,19892],{"class":114,"line":351},[112,19893,147],{"emptyLinePlaceholder":146},[112,19895,19896,19898,19901,19904,19907,19909,19912],{"class":114,"line":361},[112,19897,3385],{"class":125},[112,19899,19900],{"class":163}," expect",[112,19902,19903],{"class":129},"(page).",[112,19905,19906],{"class":163},"toHaveURL",[112,19908,425],{"class":129},[112,19910,19911],{"class":136},"'\u002Fdashboard'",[112,19913,431],{"class":129},[112,19915,19916,19918,19920,19923,19926,19928,19931,19934,19937],{"class":114,"line":367},[112,19917,3385],{"class":125},[112,19919,19900],{"class":163},[112,19921,19922],{"class":129},"(page.",[112,19924,19925],{"class":163},"locator",[112,19927,425],{"class":129},[112,19929,19930],{"class":136},"'.user-name'",[112,19932,19933],{"class":129},")).",[112,19935,19936],{"class":163},"toBeVisible",[112,19938,630],{"class":129},[112,19940,19941],{"class":114,"line":373},[112,19942,19943],{"class":129},"  }))\n",[112,19945,19946],{"class":114,"line":379},[112,19947,147],{"emptyLinePlaceholder":146},[112,19949,19950,19952,19954,19957,19959,19961,19963,19966,19968,19970,19972,19974,19976,19978],{"class":114,"line":1302},[112,19951,19787],{"class":163},[112,19953,425],{"class":129},[112,19955,19956],{"class":156},"2",[112,19958,447],{"class":129},[112,19960,19796],{"class":163},[112,19962,425],{"class":129},[112,19964,19965],{"class":136},"'failed login with invalid credentials'",[112,19967,447],{"class":129},[112,19969,3305],{"class":125},[112,19971,2253],{"class":129},[112,19973,19810],{"class":222},[112,19975,2259],{"class":129},[112,19977,229],{"class":125},[112,19979,1294],{"class":129},[112,19981,19982,19984,19986,19988,19990,19992],{"class":114,"line":1502},[112,19983,3385],{"class":125},[112,19985,19823],{"class":129},[112,19987,19826],{"class":163},[112,19989,425],{"class":129},[112,19991,19831],{"class":136},[112,19993,431],{"class":129},[112,19995,19996,19998,20000,20002,20004,20006,20008,20011],{"class":114,"line":1507},[112,19997,3385],{"class":125},[112,19999,19823],{"class":129},[112,20001,19842],{"class":163},[112,20003,425],{"class":129},[112,20005,19847],{"class":136},[112,20007,447],{"class":129},[112,20009,20010],{"class":136},"'invalid@example.com'",[112,20012,431],{"class":129},[112,20014,20015,20017,20019,20021,20023,20025,20027,20030],{"class":114,"line":1512},[112,20016,3385],{"class":125},[112,20018,19823],{"class":129},[112,20020,19842],{"class":163},[112,20022,425],{"class":129},[112,20024,19866],{"class":136},[112,20026,447],{"class":129},[112,20028,20029],{"class":136},"'wrongpassword'",[112,20031,431],{"class":129},[112,20033,20034,20036,20038,20040,20042,20044],{"class":114,"line":1518},[112,20035,3385],{"class":125},[112,20037,19823],{"class":129},[112,20039,19882],{"class":163},[112,20041,425],{"class":129},[112,20043,19887],{"class":136},[112,20045,431],{"class":129},[112,20047,20048],{"class":114,"line":1524},[112,20049,147],{"emptyLinePlaceholder":146},[112,20051,20052,20054,20056,20058,20060,20062,20065,20067,20070,20072,20075],{"class":114,"line":1530},[112,20053,3385],{"class":125},[112,20055,19900],{"class":163},[112,20057,19922],{"class":129},[112,20059,19925],{"class":163},[112,20061,425],{"class":129},[112,20063,20064],{"class":136},"'.error-message'",[112,20066,19933],{"class":129},[112,20068,20069],{"class":163},"toContainText",[112,20071,425],{"class":129},[112,20073,20074],{"class":136},"'Invalid credentials'",[112,20076,431],{"class":129},[112,20078,20079],{"class":114,"line":1536},[112,20080,19943],{"class":129},[112,20082,20083],{"class":114,"line":1542},[112,20084,8436],{"class":129},[11,20086,20088],{"id":20087},"実践的な活用tips","実践的な活用Tips",[83,20090,20092],{"id":20091},"_1-テストケースのタグ付け","1. テストケースのタグ付け",[15,20094,20095],{},"タグを活用して、テストケースを柔軟に分類できます。",[103,20097,20100],{"className":20098,"code":20099,"language":5951},[5949],"Tags: #smoke, #critical, #api, #authentication\n",[90,20101,20099],{"__ignoreMap":108},[15,20103,20104],{},"フィルタリング例：",[31,20106,20107,20110,20113],{},[34,20108,20109],{},"スモークテストだけを実行",[34,20111,20112],{},"重要度の高いテストだけを選択",[34,20114,20115],{},"API関連のテストケースを検索",[83,20117,20119],{"id":20118},"_2-カスタムフィールドの活用","2. カスタムフィールドの活用",[15,20121,20122],{},"プロジェクト固有の情報を追加できます。",[103,20124,20127],{"className":20125,"code":20126,"language":5951},[5949],"Custom Fields:\n- Test Data Set: Dataset A\n- Browser: Chrome, Firefox, Safari\n- API Version: v2.0\n",[90,20128,20126],{"__ignoreMap":108},[83,20130,20132],{"id":20131},"_3-テストケースの再利用","3. テストケースの再利用",[15,20134,20135],{},"共通のテストケースをShared Stepsとして定義し、複数のテストケースで再利用できます。",[103,20137,20140],{"className":20138,"code":20139,"language":5951},[5949],"Shared Step: ログイン処理\n1. ログインページにアクセス\n2. 認証情報を入力\n3. ログインボタンをクリック\n4. ダッシュボードに遷移することを確認\n",[90,20141,20139],{"__ignoreMap":108},[83,20143,20145],{"id":20144},"_4-ミルストーンの設定","4. ミルストーンの設定",[15,20147,20148],{},"リリースやスプリントごとにミルストーンを設定し、テストケースを紐付けます。",[103,20150,20153],{"className":20151,"code":20152,"language":5951},[5949],"Milestones:\n- v1.0.0 (2024\u002F01\u002F15)\n- v1.1.0 (2024\u002F02\u002F15)\n- v2.0.0 (2024\u002F03\u002F31)\n",[90,20154,20152],{"__ignoreMap":108},[11,20156,20158],{"id":20157},"スプレッドシート-vs-qase","スプレッドシート vs Qase",[7735,20160,20161,20173],{},[7738,20162,20163],{},[7741,20164,20165,20168,20171],{},[7744,20166,20167],{},"項目",[7744,20169,20170],{},"スプレッドシート",[7744,20172,18927],{},[7754,20174,20175,20186,20197,20208,20219,20230,20241],{},[7741,20176,20177,20180,20183],{},[7759,20178,20179],{},"初期コスト",[7759,20181,20182],{},"無料",[7759,20184,20185],{},"3ユーザーまで無料",[7741,20187,20188,20191,20194],{},[7759,20189,20190],{},"バージョン管理",[7759,20192,20193],{},"手動、履歴追跡が困難",[7759,20195,20196],{},"自動、変更履歴を完全追跡",[7741,20198,20199,20202,20205],{},[7759,20200,20201],{},"テスト結果の集計",[7759,20203,20204],{},"手作業、時間がかかる",[7759,20206,20207],{},"自動集計、リアルタイム更新",[7741,20209,20210,20213,20216],{},[7759,20211,20212],{},"自動テスト連携",[7759,20214,20215],{},"困難",[7759,20217,20218],{},"API連携で自動化可能",[7741,20220,20221,20224,20227],{},[7759,20222,20223],{},"検索性",[7759,20225,20226],{},"低い",[7759,20228,20229],{},"高度な検索・フィルタ機能",[7741,20231,20232,20235,20238],{},[7759,20233,20234],{},"テストケースの品質",[7759,20236,20237],{},"ばらつきあり",[7759,20239,20240],{},"テンプレートで標準化",[7741,20242,20243,20246,20249],{},[7759,20244,20245],{},"複数人での同時編集",[7759,20247,20248],{},"可能だが競合しやすい",[7759,20250,20251],{},"競合なく同時編集可能",[11,20253,20254],{"id":20254},"導入時の注意点",[83,20256,20258],{"id":20257},"_1-既存テストケースの移行","1. 既存テストケースの移行",[15,20260,20261],{},"スプレッドシートからQaseへの移行には時間がかかります。",[15,20263,20264],{},[27,20265,20266],{},"推奨アプローチ",[4108,20268,20269,20272,20275],{},[34,20270,20271],{},"重要度の高いテストケースから移行",[34,20273,20274],{},"新しいテストケースはQaseで作成",[34,20276,20277],{},"段階的に移行を進める",[83,20279,20281],{"id":20280},"_2-チームへの浸透","2. チームへの浸透",[15,20283,20284],{},"新しいツールの導入には学習コストがあります。",[15,20286,20287],{},[27,20288,20289],{},"対策",[31,20291,20292,20295,20298],{},[34,20293,20294],{},"導入説明会を実施",[34,20296,20297],{},"マニュアルを作成",[34,20299,20300],{},"スモールスタートで効果を実感してもらう",[83,20302,20304],{"id":20303},"_3-自動化の計画","3. 自動化の計画",[15,20306,20307],{},"すぐにすべてを自動化する必要はありません。",[15,20309,20310],{},[27,20311,20312],{},"段階的なアプローチ",[4108,20314,20315,20318,20321],{},[34,20316,20317],{},"手動テストをQaseで管理",[34,20319,20320],{},"重要なテストケースから自動化",[34,20322,20323],{},"CI\u002FCDと連携",[11,20325,919],{"id":919},[15,20327,20328],{},"Qaseを導入することで：",[31,20330,20331,20337,20343,20349],{},[34,20332,20333,20336],{},[27,20334,20335],{},"テスト管理の効率化",": テストケースの作成・実行・結果集計が容易に",[34,20338,20339,20342],{},[27,20340,20341],{},"品質の標準化",": テンプレートによりテストケースの品質を一定に保つ",[34,20344,20345,20348],{},[27,20346,20347],{},"自動化の促進",": CI\u002FCDとの連携で自動テストを効果的に管理",[34,20350,20351,20354],{},[27,20352,20353],{},"可視化の向上",": ダッシュボードでテストの進捗を一目で把握",[15,20356,20357],{},"スプレッドシートでのテスト管理に限界を感じているチームには、Qaseの導入を強くおすすめします。",[83,20359,2930],{"id":2930},[31,20361,20362,20368,20375],{},[34,20363,20364],{},[759,20365,20367],{"href":18925,"rel":20366},[763],"Qase 公式サイト",[34,20369,20370],{},[759,20371,20374],{"href":20372,"rel":20373},"https:\u002F\u002Fhelp.qase.io\u002F",[763],"Qase Documentation",[34,20376,20377],{},[759,20378,20381],{"href":20379,"rel":20380},"https:\u002F\u002Fgithub.com\u002Fqase-tms",[763],"Qase Reporters",[944,20383,20384],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":108,"searchDepth":122,"depth":122,"links":20386},[20387,20388,20389,20395,20403,20407,20413,20414,20419],{"id":18881,"depth":122,"text":18881},{"id":18919,"depth":122,"text":18920},{"id":18967,"depth":122,"text":18968,"children":20390},[20391,20392,20393,20394],{"id":18971,"depth":143,"text":18972},{"id":18978,"depth":143,"text":18979},{"id":19007,"depth":143,"text":19008},{"id":19133,"depth":143,"text":19134},{"id":19154,"depth":122,"text":19154,"children":20396},[20397,20398,20399,20400,20401,20402],{"id":19157,"depth":143,"text":19158},{"id":19170,"depth":143,"text":19171},{"id":19188,"depth":143,"text":19189},{"id":19276,"depth":143,"text":19277},{"id":19289,"depth":143,"text":19290},{"id":19302,"depth":143,"text":19303},{"id":19362,"depth":122,"text":19363,"children":20404},[20405,20406],{"id":19366,"depth":143,"text":19367},{"id":19598,"depth":143,"text":19599},{"id":20087,"depth":122,"text":20088,"children":20408},[20409,20410,20411,20412],{"id":20091,"depth":143,"text":20092},{"id":20118,"depth":143,"text":20119},{"id":20131,"depth":143,"text":20132},{"id":20144,"depth":143,"text":20145},{"id":20157,"depth":122,"text":20158},{"id":20254,"depth":122,"text":20254,"children":20415},[20416,20417,20418],{"id":20257,"depth":143,"text":20258},{"id":20280,"depth":143,"text":20281},{"id":20303,"depth":143,"text":20304},{"id":919,"depth":122,"text":919,"children":20420},[20421],{"id":2930,"depth":143,"text":2930},"2022-02-20","テスト管理ツールQaseを導入して、スプレッドシートによるテスト管理から脱却した経験と、その導入方法・活用方法について解説します。",{"tags":20425},[20426,20427,20428],"testing","test-management","qase","\u002Fblog\u002Fqase-test-management-tool",{"title":18876,"description":20423},"blog\u002Fqase-test-management-tool","HeLBalnmLrwL6EmcEM1LIcmzWA2OQjk_vPvNUDsXVb0",{"id":20434,"title":20435,"body":20436,"date":21388,"description":21389,"extension":962,"meta":21390,"navigation":146,"path":21395,"seo":21396,"stem":21397,"__hash__":21398},"blog\u002Fblog\u002Fcloud-functions-serverless-app.md","Google Cloud FunctionsとCloud SQLの連携",{"type":8,"value":20437,"toc":21372},[20438,20442,20445,20449,20475,20479,20482,20563,20566,20578,20582,20585,20588,20591,20686,20689,20898,20901,21179,21181,21184,21257,21259,21263,21266,21318,21322,21325,21329,21332,21334,21337,21342,21353,21358,21369],[11,20439,20441],{"id":20440},"cloud-functionsとは","Cloud Functionsとは",[15,20443,20444],{},"Google Cloud FunctionsはGCPが提供するサーバーレス実行環境です。",[15,20446,20447],{},[27,20448,2608],{},[31,20450,20451,20457,20463,20469],{},[34,20452,20453,20456],{},[27,20454,20455],{},"サーバー管理不要"," インフラの管理や運用が不要",[34,20458,20459,20462],{},[27,20460,20461],{},"自動スケーリング"," 負荷に応じて自動的にスケール",[34,20464,20465,20468],{},[27,20466,20467],{},"従量課金"," 実行時間に応じた課金モデル",[34,20470,20471,20474],{},[27,20472,20473],{},"イベント駆動"," HTTPリクエストやCloud Storageの変更などをトリガーに実行",[11,20476,20478],{"id":20477},"基本的なhttp関数","基本的なHTTP関数",[15,20480,20481],{},"最もシンプルなHTTPトリガーの例です。",[103,20483,20487],{"className":20484,"code":20485,"language":20486,"meta":108,"style":108},"language-js shiki shiki-themes github-light github-dark","exports.helloWorld = (req, res) => {\n  const message = req.query.message || req.body.message || 'Hello GCP!';\n  res.status(200).send(message);\n};\n","js",[90,20488,20489,20516,20540,20559],{"__ignoreMap":108},[112,20490,20491,20493,20495,20498,20500,20502,20505,20507,20510,20512,20514],{"class":114,"line":115},[112,20492,16473],{"class":156},[112,20494,2124],{"class":129},[112,20496,20497],{"class":163},"helloWorld",[112,20499,160],{"class":125},[112,20501,3945],{"class":129},[112,20503,20504],{"class":222},"req",[112,20506,447],{"class":129},[112,20508,20509],{"class":222},"res",[112,20511,226],{"class":129},[112,20513,229],{"class":125},[112,20515,1294],{"class":129},[112,20517,20518,20520,20523,20525,20528,20530,20533,20535,20538],{"class":114,"line":122},[112,20519,1974],{"class":125},[112,20521,20522],{"class":156}," message",[112,20524,160],{"class":125},[112,20526,20527],{"class":129}," req.query.message ",[112,20529,4284],{"class":125},[112,20531,20532],{"class":129}," req.body.message ",[112,20534,4284],{"class":125},[112,20536,20537],{"class":136}," 'Hello GCP!'",[112,20539,140],{"class":129},[112,20541,20542,20545,20547,20549,20552,20554,20556],{"class":114,"line":143},[112,20543,20544],{"class":129},"  res.",[112,20546,5337],{"class":163},[112,20548,425],{"class":129},[112,20550,20551],{"class":156},"200",[112,20553,610],{"class":129},[112,20555,18021],{"class":163},[112,20557,20558],{"class":129},"(message);\n",[112,20560,20561],{"class":114,"line":150},[112,20562,1305],{"class":129},[15,20564,20565],{},"デプロイ後、トリガーURLにアクセスすることで関数が実行されます。",[103,20567,20569],{"className":771,"code":20568,"language":773,"meta":108,"style":108},"curl https:\u002F\u002FREGION-PROJECT_ID.cloudfunctions.net\u002FhelloWorld?message=こんにちは\n",[90,20570,20571],{"__ignoreMap":108},[112,20572,20573,20575],{"class":114,"line":115},[112,20574,14330],{"class":163},[112,20576,20577],{"class":136}," https:\u002F\u002FREGION-PROJECT_ID.cloudfunctions.net\u002FhelloWorld?message=こんにちは\n",[11,20579,20581],{"id":20580},"cloud-sqlとの連携","Cloud SQLとの連携",[15,20583,20584],{},"Cloud FunctionsからCloud SQLに接続する方法を紹介します。",[83,20586,20587],{"id":20587},"接続設定",[15,20589,20590],{},"Cloud SQLへの接続には、Unix Domainソケットを使用します。",[103,20592,20594],{"className":20484,"code":20593,"language":20486,"meta":108,"style":108},"const mysql = require('mysql');\n\nconst connection = mysql.createConnection({\n  socketPath: `\u002Fcloudsql\u002F${process.env.CLOUD_SQL_CONNECTION_NAME}`,\n  user: process.env.DB_USER,\n  password: process.env.DB_PASSWORD,\n  database: process.env.DB_NAME\n});\n",[90,20595,20596,20614,20618,20634,20657,20667,20675,20682],{"__ignoreMap":108},[112,20597,20598,20600,20603,20605,20608,20610,20612],{"class":114,"line":115},[112,20599,153],{"class":125},[112,20601,20602],{"class":156}," mysql",[112,20604,160],{"class":125},[112,20606,20607],{"class":163}," require",[112,20609,425],{"class":129},[112,20611,15825],{"class":136},[112,20613,635],{"class":129},[112,20615,20616],{"class":114,"line":122},[112,20617,147],{"emptyLinePlaceholder":146},[112,20619,20620,20622,20624,20626,20629,20632],{"class":114,"line":143},[112,20621,153],{"class":125},[112,20623,10114],{"class":156},[112,20625,160],{"class":125},[112,20627,20628],{"class":129}," mysql.",[112,20630,20631],{"class":163},"createConnection",[112,20633,167],{"class":129},[112,20635,20636,20639,20642,20644,20646,20648,20650,20653,20655],{"class":114,"line":150},[112,20637,20638],{"class":129},"  socketPath: ",[112,20640,20641],{"class":136},"`\u002Fcloudsql\u002F${",[112,20643,13621],{"class":129},[112,20645,2124],{"class":136},[112,20647,13626],{"class":129},[112,20649,2124],{"class":136},[112,20651,20652],{"class":156},"CLOUD_SQL_CONNECTION_NAME",[112,20654,592],{"class":136},[112,20656,179],{"class":129},[112,20658,20659,20662,20665],{"class":114,"line":170},[112,20660,20661],{"class":129},"  user: process.env.",[112,20663,20664],{"class":156},"DB_USER",[112,20666,179],{"class":129},[112,20668,20669,20671,20673],{"class":114,"line":182},[112,20670,16530],{"class":129},[112,20672,15636],{"class":156},[112,20674,179],{"class":129},[112,20676,20677,20679],{"class":114,"line":193},[112,20678,16543],{"class":129},[112,20680,20681],{"class":156},"DB_NAME\n",[112,20683,20684],{"class":114,"line":205},[112,20685,250],{"class":129},[83,20687,20688],{"id":20688},"データ取得の実装例",[103,20690,20692],{"className":20484,"code":20691,"language":20486,"meta":108,"style":108},"exports.getUsers = async (req, res) => {\n  const connection = mysql.createConnection({\n    socketPath: `\u002Fcloudsql\u002F${process.env.CLOUD_SQL_CONNECTION_NAME}`,\n    user: process.env.DB_USER,\n    password: process.env.DB_PASSWORD,\n    database: process.env.DB_NAME\n  });\n\n  connection.query('SELECT * FROM users', (err, results) => {\n    if (err) {\n      console.error(err);\n      res.status(500).json({ error: 'Database error' });\n      return;\n    }\n    res.json(results);\n  });\n\n  connection.end();\n};\n",[90,20693,20694,20721,20735,20756,20764,20773,20780,20784,20788,20817,20824,20834,20857,20863,20867,20877,20881,20885,20894],{"__ignoreMap":108},[112,20695,20696,20698,20700,20703,20705,20707,20709,20711,20713,20715,20717,20719],{"class":114,"line":115},[112,20697,16473],{"class":156},[112,20699,2124],{"class":129},[112,20701,20702],{"class":163},"getUsers",[112,20704,160],{"class":125},[112,20706,1955],{"class":125},[112,20708,3945],{"class":129},[112,20710,20504],{"class":222},[112,20712,447],{"class":129},[112,20714,20509],{"class":222},[112,20716,226],{"class":129},[112,20718,229],{"class":125},[112,20720,1294],{"class":129},[112,20722,20723,20725,20727,20729,20731,20733],{"class":114,"line":122},[112,20724,1974],{"class":125},[112,20726,10114],{"class":156},[112,20728,160],{"class":125},[112,20730,20628],{"class":129},[112,20732,20631],{"class":163},[112,20734,167],{"class":129},[112,20736,20737,20740,20742,20744,20746,20748,20750,20752,20754],{"class":114,"line":143},[112,20738,20739],{"class":129},"    socketPath: ",[112,20741,20641],{"class":136},[112,20743,13621],{"class":129},[112,20745,2124],{"class":136},[112,20747,13626],{"class":129},[112,20749,2124],{"class":136},[112,20751,20652],{"class":156},[112,20753,592],{"class":136},[112,20755,179],{"class":129},[112,20757,20758,20760,20762],{"class":114,"line":150},[112,20759,15618],{"class":129},[112,20761,20664],{"class":156},[112,20763,179],{"class":129},[112,20765,20766,20769,20771],{"class":114,"line":170},[112,20767,20768],{"class":129},"    password: process.env.",[112,20770,15636],{"class":156},[112,20772,179],{"class":129},[112,20774,20775,20778],{"class":114,"line":182},[112,20776,20777],{"class":129},"    database: process.env.",[112,20779,20681],{"class":156},[112,20781,20782],{"class":114,"line":193},[112,20783,1495],{"class":129},[112,20785,20786],{"class":114,"line":205},[112,20787,147],{"emptyLinePlaceholder":146},[112,20789,20790,20793,20795,20797,20800,20803,20806,20808,20811,20813,20815],{"class":114,"line":241},[112,20791,20792],{"class":129},"  connection.",[112,20794,4339],{"class":163},[112,20796,425],{"class":129},[112,20798,20799],{"class":136},"'SELECT * FROM users'",[112,20801,20802],{"class":129},", (",[112,20804,20805],{"class":222},"err",[112,20807,447],{"class":129},[112,20809,20810],{"class":222},"results",[112,20812,226],{"class":129},[112,20814,229],{"class":125},[112,20816,1294],{"class":129},[112,20818,20819,20821],{"class":114,"line":247},[112,20820,3511],{"class":125},[112,20822,20823],{"class":129}," (err) {\n",[112,20825,20826,20829,20831],{"class":114,"line":351},[112,20827,20828],{"class":129},"      console.",[112,20830,4878],{"class":163},[112,20832,20833],{"class":129},"(err);\n",[112,20835,20836,20839,20841,20843,20845,20847,20849,20852,20855],{"class":114,"line":361},[112,20837,20838],{"class":129},"      res.",[112,20840,5337],{"class":163},[112,20842,425],{"class":129},[112,20844,3869],{"class":156},[112,20846,610],{"class":129},[112,20848,2958],{"class":163},[112,20850,20851],{"class":129},"({ error: ",[112,20853,20854],{"class":136},"'Database error'",[112,20856,4353],{"class":129},[112,20858,20859,20861],{"class":114,"line":367},[112,20860,5009],{"class":125},[112,20862,140],{"class":129},[112,20864,20865],{"class":114,"line":373},[112,20866,3118],{"class":129},[112,20868,20869,20872,20874],{"class":114,"line":379},[112,20870,20871],{"class":129},"    res.",[112,20873,2958],{"class":163},[112,20875,20876],{"class":129},"(results);\n",[112,20878,20879],{"class":114,"line":1302},[112,20880,1495],{"class":129},[112,20882,20883],{"class":114,"line":1502},[112,20884,147],{"emptyLinePlaceholder":146},[112,20886,20887,20889,20892],{"class":114,"line":1507},[112,20888,20792],{"class":129},[112,20890,20891],{"class":163},"end",[112,20893,462],{"class":129},[112,20895,20896],{"class":114,"line":1512},[112,20897,1305],{"class":129},[83,20899,20900],{"id":20900},"データ登録の実装例",[103,20902,20904],{"className":20484,"code":20903,"language":20486,"meta":108,"style":108},"exports.createUser = async (req, res) => {\n  const { name } = req.body;\n\n  if (!name) {\n    res.status(400).json({ error: 'Name is required' });\n    return;\n  }\n\n  const connection = mysql.createConnection({\n    socketPath: `\u002Fcloudsql\u002F${process.env.CLOUD_SQL_CONNECTION_NAME}`,\n    user: process.env.DB_USER,\n    password: process.env.DB_PASSWORD,\n    database: process.env.DB_NAME\n  });\n\n  connection.query(\n    'INSERT INTO users (name) VALUES (?)',\n    [name],\n    (err, result) => {\n      if (err) {\n        console.error(err);\n        res.status(500).json({ error: 'Database error' });\n        return;\n      }\n      res.json({ id: result.insertId, name });\n    }\n  );\n\n  connection.end();\n};\n",[90,20905,20906,20932,20947,20951,20962,20983,20989,20993,20997,21011,21031,21039,21047,21053,21057,21061,21069,21076,21081,21099,21105,21114,21135,21142,21146,21155,21159,21163,21167,21175],{"__ignoreMap":108},[112,20907,20908,20910,20912,20914,20916,20918,20920,20922,20924,20926,20928,20930],{"class":114,"line":115},[112,20909,16473],{"class":156},[112,20911,2124],{"class":129},[112,20913,10324],{"class":163},[112,20915,160],{"class":125},[112,20917,1955],{"class":125},[112,20919,3945],{"class":129},[112,20921,20504],{"class":222},[112,20923,447],{"class":129},[112,20925,20509],{"class":222},[112,20927,226],{"class":129},[112,20929,229],{"class":125},[112,20931,1294],{"class":129},[112,20933,20934,20936,20938,20940,20942,20944],{"class":114,"line":122},[112,20935,1974],{"class":125},[112,20937,561],{"class":129},[112,20939,16383],{"class":156},[112,20941,573],{"class":129},[112,20943,576],{"class":125},[112,20945,20946],{"class":129}," req.body;\n",[112,20948,20949],{"class":114,"line":143},[112,20950,147],{"emptyLinePlaceholder":146},[112,20952,20953,20955,20957,20959],{"class":114,"line":150},[112,20954,3367],{"class":125},[112,20956,3945],{"class":129},[112,20958,3948],{"class":125},[112,20960,20961],{"class":129},"name) {\n",[112,20963,20964,20966,20968,20970,20972,20974,20976,20978,20981],{"class":114,"line":170},[112,20965,20871],{"class":129},[112,20967,5337],{"class":163},[112,20969,425],{"class":129},[112,20971,18209],{"class":156},[112,20973,610],{"class":129},[112,20975,2958],{"class":163},[112,20977,20851],{"class":129},[112,20979,20980],{"class":136},"'Name is required'",[112,20982,4353],{"class":129},[112,20984,20985,20987],{"class":114,"line":182},[112,20986,3973],{"class":125},[112,20988,140],{"class":129},[112,20990,20991],{"class":114,"line":193},[112,20992,3232],{"class":129},[112,20994,20995],{"class":114,"line":205},[112,20996,147],{"emptyLinePlaceholder":146},[112,20998,20999,21001,21003,21005,21007,21009],{"class":114,"line":241},[112,21000,1974],{"class":125},[112,21002,10114],{"class":156},[112,21004,160],{"class":125},[112,21006,20628],{"class":129},[112,21008,20631],{"class":163},[112,21010,167],{"class":129},[112,21012,21013,21015,21017,21019,21021,21023,21025,21027,21029],{"class":114,"line":247},[112,21014,20739],{"class":129},[112,21016,20641],{"class":136},[112,21018,13621],{"class":129},[112,21020,2124],{"class":136},[112,21022,13626],{"class":129},[112,21024,2124],{"class":136},[112,21026,20652],{"class":156},[112,21028,592],{"class":136},[112,21030,179],{"class":129},[112,21032,21033,21035,21037],{"class":114,"line":351},[112,21034,15618],{"class":129},[112,21036,20664],{"class":156},[112,21038,179],{"class":129},[112,21040,21041,21043,21045],{"class":114,"line":361},[112,21042,20768],{"class":129},[112,21044,15636],{"class":156},[112,21046,179],{"class":129},[112,21048,21049,21051],{"class":114,"line":367},[112,21050,20777],{"class":129},[112,21052,20681],{"class":156},[112,21054,21055],{"class":114,"line":373},[112,21056,1495],{"class":129},[112,21058,21059],{"class":114,"line":379},[112,21060,147],{"emptyLinePlaceholder":146},[112,21062,21063,21065,21067],{"class":114,"line":1302},[112,21064,20792],{"class":129},[112,21066,4339],{"class":163},[112,21068,2304],{"class":129},[112,21070,21071,21074],{"class":114,"line":1502},[112,21072,21073],{"class":136},"    'INSERT INTO users (name) VALUES (?)'",[112,21075,179],{"class":129},[112,21077,21078],{"class":114,"line":1507},[112,21079,21080],{"class":129},"    [name],\n",[112,21082,21083,21086,21088,21090,21093,21095,21097],{"class":114,"line":1512},[112,21084,21085],{"class":129},"    (",[112,21087,20805],{"class":222},[112,21089,447],{"class":129},[112,21091,21092],{"class":222},"result",[112,21094,226],{"class":129},[112,21096,229],{"class":125},[112,21098,1294],{"class":129},[112,21100,21101,21103],{"class":114,"line":1518},[112,21102,3618],{"class":125},[112,21104,20823],{"class":129},[112,21106,21107,21110,21112],{"class":114,"line":1524},[112,21108,21109],{"class":129},"        console.",[112,21111,4878],{"class":163},[112,21113,20833],{"class":129},[112,21115,21116,21119,21121,21123,21125,21127,21129,21131,21133],{"class":114,"line":1530},[112,21117,21118],{"class":129},"        res.",[112,21120,5337],{"class":163},[112,21122,425],{"class":129},[112,21124,3869],{"class":156},[112,21126,610],{"class":129},[112,21128,2958],{"class":163},[112,21130,20851],{"class":129},[112,21132,20854],{"class":136},[112,21134,4353],{"class":129},[112,21136,21137,21140],{"class":114,"line":1536},[112,21138,21139],{"class":125},"        return",[112,21141,140],{"class":129},[112,21143,21144],{"class":114,"line":1542},[112,21145,3643],{"class":129},[112,21147,21148,21150,21152],{"class":114,"line":2402},[112,21149,20838],{"class":129},[112,21151,2958],{"class":163},[112,21153,21154],{"class":129},"({ id: result.insertId, name });\n",[112,21156,21157],{"class":114,"line":2407},[112,21158,3118],{"class":129},[112,21160,21161],{"class":114,"line":2413},[112,21162,2039],{"class":129},[112,21164,21165],{"class":114,"line":2446},[112,21166,147],{"emptyLinePlaceholder":146},[112,21168,21169,21171,21173],{"class":114,"line":2451},[112,21170,20792],{"class":129},[112,21172,20891],{"class":163},[112,21174,462],{"class":129},[112,21176,21177],{"class":114,"line":2464},[112,21178,1305],{"class":129},[11,21180,9742],{"id":9742},[15,21182,21183],{},"機密情報は環境変数で管理します。",[103,21185,21187],{"className":771,"code":21186,"language":773,"meta":108,"style":108},"gcloud functions deploy myFunction \\\n  --runtime nodejs18 \\\n  --trigger-http \\\n  --set-env-vars CLOUD_SQL_CONNECTION_NAME=project:region:instance \\\n  --set-env-vars DB_USER=myuser \\\n  --set-env-vars DB_PASSWORD=mypassword \\\n  --set-env-vars DB_NAME=mydatabase\n",[90,21188,21189,21205,21215,21222,21232,21241,21250],{"__ignoreMap":108},[112,21190,21191,21194,21197,21200,21203],{"class":114,"line":115},[112,21192,21193],{"class":163},"gcloud",[112,21195,21196],{"class":136}," functions",[112,21198,21199],{"class":136}," deploy",[112,21201,21202],{"class":136}," myFunction",[112,21204,8535],{"class":156},[112,21206,21207,21210,21213],{"class":114,"line":122},[112,21208,21209],{"class":156},"  --runtime",[112,21211,21212],{"class":136}," nodejs18",[112,21214,8535],{"class":156},[112,21216,21217,21220],{"class":114,"line":143},[112,21218,21219],{"class":156},"  --trigger-http",[112,21221,8535],{"class":156},[112,21223,21224,21227,21230],{"class":114,"line":150},[112,21225,21226],{"class":156},"  --set-env-vars",[112,21228,21229],{"class":136}," CLOUD_SQL_CONNECTION_NAME=project:region:instance",[112,21231,8535],{"class":156},[112,21233,21234,21236,21239],{"class":114,"line":170},[112,21235,21226],{"class":156},[112,21237,21238],{"class":136}," DB_USER=myuser",[112,21240,8535],{"class":156},[112,21242,21243,21245,21248],{"class":114,"line":182},[112,21244,21226],{"class":156},[112,21246,21247],{"class":136}," DB_PASSWORD=mypassword",[112,21249,8535],{"class":156},[112,21251,21252,21254],{"class":114,"line":193},[112,21253,21226],{"class":156},[112,21255,21256],{"class":136}," DB_NAME=mydatabase\n",[11,21258,12004],{"id":12004},[83,21260,21262],{"id":21261},"_1-プリペアドステートメントの使用","1. プリペアドステートメントの使用",[15,21264,21265],{},"SQLインジェクション対策として、必ずプリペアドステートメントを使用します。",[103,21267,21269],{"className":20484,"code":21268,"language":20486,"meta":108,"style":108},"\u002F\u002F ❌ 危険: SQLインジェクションのリスク\nconnection.query(`INSERT INTO users (name) VALUES('${name}')`)\n\n\u002F\u002F ✅ 安全: プリペアドステートメント\nconnection.query('INSERT INTO users (name) VALUES (?)', [name])\n",[90,21270,21271,21276,21295,21299,21304],{"__ignoreMap":108},[112,21272,21273],{"class":114,"line":115},[112,21274,21275],{"class":118},"\u002F\u002F ❌ 危険: SQLインジェクションのリスク\n",[112,21277,21278,21281,21283,21285,21288,21290,21293],{"class":114,"line":122},[112,21279,21280],{"class":129},"connection.",[112,21282,4339],{"class":163},[112,21284,425],{"class":129},[112,21286,21287],{"class":136},"`INSERT INTO users (name) VALUES('${",[112,21289,16383],{"class":129},[112,21291,21292],{"class":136},"}')`",[112,21294,431],{"class":129},[112,21296,21297],{"class":114,"line":143},[112,21298,147],{"emptyLinePlaceholder":146},[112,21300,21301],{"class":114,"line":150},[112,21302,21303],{"class":118},"\u002F\u002F ✅ 安全: プリペアドステートメント\n",[112,21305,21306,21308,21310,21312,21315],{"class":114,"line":170},[112,21307,21280],{"class":129},[112,21309,4339],{"class":163},[112,21311,425],{"class":129},[112,21313,21314],{"class":136},"'INSERT INTO users (name) VALUES (?)'",[112,21316,21317],{"class":129},", [name])\n",[83,21319,21321],{"id":21320},"_2-環境変数の活用","2. 環境変数の活用",[15,21323,21324],{},"データベース接続情報はコードに直接記述せず、環境変数で管理します。",[83,21326,21328],{"id":21327},"_3-cloud-sql-proxyの利用","3. Cloud SQL Proxyの利用",[15,21330,21331],{},"本番環境では、Cloud SQL Proxyを使用して安全に接続します。",[11,21333,919],{"id":919},[15,21335,21336],{},"Cloud FunctionsとCloud SQLを組み合わせることで、サーバーレスなデータベースアプリケーションを構築できます。",[15,21338,21339],{},[27,21340,21341],{},"メリット",[31,21343,21344,21347,21350],{},[34,21345,21346],{},"インフラ管理が不要",[34,21348,21349],{},"自動スケーリングで可用性向上",[34,21351,21352],{},"従量課金でコスト最適化",[15,21354,21355],{},[27,21356,21357],{},"注意点",[31,21359,21360,21363,21366],{},[34,21361,21362],{},"コールドスタート時のレイテンシ",[34,21364,21365],{},"実行時間の制限（最大540秒）",[34,21367,21368],{},"同時実行数の上限",[944,21370,21371],{},"html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":108,"searchDepth":122,"depth":122,"links":21373},[21374,21375,21376,21381,21382,21387],{"id":20440,"depth":122,"text":20441},{"id":20477,"depth":122,"text":20478},{"id":20580,"depth":122,"text":20581,"children":21377},[21378,21379,21380],{"id":20587,"depth":143,"text":20587},{"id":20688,"depth":143,"text":20688},{"id":20900,"depth":143,"text":20900},{"id":9742,"depth":122,"text":9742},{"id":12004,"depth":122,"text":12004,"children":21383},[21384,21385,21386],{"id":21261,"depth":143,"text":21262},{"id":21320,"depth":143,"text":21321},{"id":21327,"depth":143,"text":21328},{"id":919,"depth":122,"text":919},"2022-01-31","GCPのCloud Functionsを使ったサーバーレスアプリケーションの構築方法と、Cloud SQLとの連携パターンについて解説します。",{"tags":21391},[21392,21393,21394],"gcp","serverless","cloud-functions","\u002Fblog\u002Fcloud-functions-serverless-app",{"title":20435,"description":21389},"blog\u002Fcloud-functions-serverless-app","C96i6ueDhX72R6ls8EUjga5ZrbcQoX8Tl2K0iH4PzGU",{"id":21400,"title":21401,"body":21402,"date":21924,"description":21925,"extension":962,"meta":21926,"navigation":146,"path":21930,"seo":21931,"stem":21932,"__hash__":21933},"blog\u002Fblog\u002Fzerossl-free-ssl-certificate-setup.md","ZeroSSLで無料SSL証明書を取得してGCPに適用する",{"type":8,"value":21403,"toc":21893},[21404,21406,21414,21418,21421,21425,21433,21438,21446,21448,21462,21464,21475,21479,21483,21490,21494,21508,21512,21519,21523,21530,21534,21537,21541,21544,21549,21588,21592,21595,21598,21601,21605,21608,21628,21632,21636,21678,21683,21705,21710,21714,21744,21748,21755,21758,21761,21764,21778,21781,21790,21811,21814,21817,21828,21831,21839,21842,21853,21855,21858,21863,21874,21879,21887,21890],[11,21405,13],{"id":13},[15,21407,21408,21413],{},[759,21409,21412],{"href":21410,"rel":21411},"https:\u002F\u002Fzerossl.com\u002F",[763],"ZeroSSL","を使って無料のSSL証明書を発行し、GCP環境にHTTPSを導入する方法を解説します。",[11,21415,21417],{"id":21416},"zerosslとは","ZeroSSLとは",[15,21419,21420],{},"ZeroSSLは、Let's Encryptと同様に無料でSSL証明書を発行できるサービスです。",[83,21422,21424],{"id":21423},"lets-encryptとの違い","Let's Encryptとの違い",[15,21426,21427,21432],{},[759,21428,21431],{"href":21429,"rel":21430},"https:\u002F\u002Fscotthelme.co.uk\u002Fintroducing-another-free-ca-as-an-alternative-to-lets-encrypt\u002F",[763],"Scott Helme氏の記事","では、以下のように述べられています。",[6351,21434,21435],{},[15,21436,21437],{},"Let's Encryptは、証明書を無料で大規模に提供することで、素晴らしい活動をしている素晴らしい組織です。しかし、問題は、長い間、そのような組織は彼らだけだったということです。他の選択肢を持つことは常に良いアイデアです。",[15,21439,21440,21441],{},"参考記事: ",[759,21442,21445],{"href":21443,"rel":21444},"https:\u002F\u002Fzenn.dev\u002Fmattn\u002Farticles\u002Fb2c4c92c9116b1",[763],"ZeroSSL を使ってみた",[83,21447,2608],{"id":2608},[31,21449,21450,21453,21456,21459],{},[34,21451,21452],{},"無料プランで90日間有効な証明書を発行可能",[34,21454,21455],{},"Webブラウザベースで証明書を管理",[34,21457,21458],{},"DNS（CNAME）、HTTP、メールの3つの認証方法に対応",[34,21460,21461],{},"複数ドメインの証明書を一元管理",[11,21463,7094],{"id":7094},[31,21465,21466,21469,21472],{},[34,21467,21468],{},"GCPプロジェクトが作成済み",[34,21470,21471],{},"ドメインを取得済み",[34,21473,21474],{},"Cloud Load Balancingが設定済み",[11,21476,21478],{"id":21477},"ssl証明書の発行手順","SSL証明書の発行手順",[83,21480,21482],{"id":21481},"_1-zerosslにアカウント登録","1. ZeroSSLにアカウント登録",[15,21484,21485,21489],{},[759,21486,21488],{"href":21410,"rel":21487},[763],"ZeroSSL公式サイト","にアクセスし、アカウントを作成します。",[83,21491,21493],{"id":21492},"_2-証明書の新規作成","2. 証明書の新規作成",[4108,21495,21496,21499,21505],{},[34,21497,21498],{},"ダッシュボードから「Create Free SSL Certificate」をクリック",[34,21500,21501,21502,7108],{},"証明書を発行したいドメイン名を入力（例：",[90,21503,21504],{},"example.com",[34,21506,21507],{},"「Next Step」をクリック",[83,21509,21511],{"id":21510},"_3-証明書の有効期間を選択","3. 証明書の有効期間を選択",[4108,21513,21514,21517],{},[34,21515,21516],{},"無料プランの場合は「90-Day Certificate」を選択",[34,21518,21507],{},[83,21520,21522],{"id":21521},"_4-プランの選択","4. プランの選択",[4108,21524,21525,21528],{},[34,21526,21527],{},"「Free」プランを選択",[34,21529,21507],{},[83,21531,21533],{"id":21532},"_5-ドメイン認証方法の選択","5. ドメイン認証方法の選択",[15,21535,21536],{},"3つの認証方法から選択できます",[8992,21538,21540],{"id":21539},"dns認証cname-推奨","DNS認証（CNAME）- 推奨",[15,21542,21543],{},"最も確実な方法です。ZeroSSLが提供するCNAMEレコードをDNSに追加します。",[15,21545,21546],{},[27,21547,21548],{},"手順：",[4108,21550,21551,21554,21557,21582,21585],{},[34,21552,21553],{},"「DNS (CNAME)」を選択",[34,21555,21556],{},"表示されたCNAMEレコードをメモ",[34,21558,21559,21560],{},"Cloud DNSまたは使用中のDNSサービスに以下を追加\n",[31,21561,21562,21570,21576],{},[34,21563,21564,9512,21567],{},[27,21565,21566],{},"名前",[90,21568,21569],{},"_acme-challenge.example.com",[34,21571,21572,21575],{},[27,21573,21574],{},"タイプ"," CNAME",[34,21577,21578,21581],{},[27,21579,21580],{},"値"," ZeroSSLが提供した値",[34,21583,21584],{},"DNSの反映を待つ（数分〜最大48時間）",[34,21586,21587],{},"「Verify Domain」をクリック",[8992,21589,21591],{"id":21590},"http認証","HTTP認証",[15,21593,21594],{},"Webサーバーに検証ファイルを配置する方法です。",[8992,21596,21597],{"id":21597},"メール認証",[15,21599,21600],{},"ドメインの管理者メールアドレスに送信される認証リンクをクリックする方法です。",[83,21602,21604],{"id":21603},"_6-証明書のダウンロード","6. 証明書のダウンロード",[15,21606,21607],{},"認証が完了すると、以下のファイルをダウンロードできます。",[31,21609,21610,21616,21622],{},[34,21611,21612,21615],{},[90,21613,21614],{},"certificate.crt"," - 証明書本体",[34,21617,21618,21621],{},[90,21619,21620],{},"ca_bundle.crt"," - 中間証明書",[34,21623,21624,21627],{},[90,21625,21626],{},"private.key"," - 秘密鍵",[11,21629,21631],{"id":21630},"gcp-cloud-load-balancingへの適用","GCP Cloud Load Balancingへの適用",[83,21633,21635],{"id":21634},"_1-証明書のアップロード","1. 証明書のアップロード",[4108,21637,21638,21641,21644,21647,21650],{},[34,21639,21640],{},"GCPコンソールで「ネットワークサービス > Load Balancing」を開く",[34,21642,21643],{},"対象のロードバランサーを選択",[34,21645,21646],{},"「編集」をクリック",[34,21648,21649],{},"「フロントエンド構成」セクションで「証明書を追加」をクリック",[34,21651,21652,21653],{},"以下を入力\n",[31,21654,21655,21660,21670],{},[34,21656,21657,21659],{},[27,21658,21566],{}," 任意の証明書名",[34,21661,21662,9512,21664,21666,21667,21669],{},[27,21663,9535],{},[90,21665,21614],{},"と",[90,21668,21620],{},"を結合した内容",[34,21671,21672,9512,21675,21677],{},[27,21673,21674],{},"秘密鍵",[90,21676,21626],{},"の内容",[15,21679,21680],{},[27,21681,21682],{},"証明書の結合方法：",[103,21684,21686],{"className":771,"code":21685,"language":773,"meta":108,"style":108},"cat certificate.crt ca_bundle.crt > fullchain.crt\n",[90,21687,21688],{"__ignoreMap":108},[112,21689,21690,21693,21696,21699,21702],{"class":114,"line":115},[112,21691,21692],{"class":163},"cat",[112,21694,21695],{"class":136}," certificate.crt",[112,21697,21698],{"class":136}," ca_bundle.crt",[112,21700,21701],{"class":125}," >",[112,21703,21704],{"class":136}," fullchain.crt\n",[4108,21706,21707],{"start":182},[34,21708,21709],{},"「作成」をクリック",[83,21711,21713],{"id":21712},"_2-cloud-dnsの設定","2. Cloud DNSの設定",[4108,21715,21716,21719,21722],{},[34,21717,21718],{},"「ネットワークサービス > Cloud DNS」を開く",[34,21720,21721],{},"対象のDNSゾーンを選択",[34,21723,21724,21725],{},"Aレコードを追加\n",[31,21726,21727,21733,21738],{},[34,21728,21729,9512,21731],{},[27,21730,21566],{},[90,21732,21504],{},[34,21734,21735,21737],{},[27,21736,21574],{}," A",[34,21739,21740,21743],{},[27,21741,21742],{},"データ"," Cloud Load BalancingのIPアドレス",[83,21745,21747],{"id":21746},"_3-動作確認","3. 動作確認",[15,21749,21750,21751,21754],{},"ブラウザで",[90,21752,21753],{},"https:\u002F\u002Fexample.com","にアクセスし、SSL証明書が正しく適用されていることを確認します。",[11,21756,21757],{"id":21757},"自動更新の設定",[15,21759,21760],{},"無料証明書は90日間で期限切れになるため、定期的な更新が必要です。",[83,21762,21763],{"id":21763},"更新方法",[4108,21765,21766,21769,21772,21775],{},[34,21767,21768],{},"ZeroSSLダッシュボードで期限切れ前の証明書を確認",[34,21770,21771],{},"「Renew」ボタンをクリック",[34,21773,21774],{},"同じ手順で新しい証明書を発行",[34,21776,21777],{},"Cloud Load Balancingの証明書を更新",[83,21779,21780],{"id":21780},"更新の自動化",[15,21782,21783,21784,21789],{},"ZeroSSLは",[759,21785,21788],{"href":21786,"rel":21787},"https:\u002F\u002Fzerossl.com\u002Fdocumentation\u002Fapi\u002F",[763],"API","を提供しているため、スクリプトで自動化することも可能です。",[103,21791,21793],{"className":771,"code":21792,"language":773,"meta":108,"style":108},"# 例：証明書の有効期限を確認するスクリプト\ncurl -X GET \"https:\u002F\u002Fapi.zerossl.com\u002Fcertificates?access_key=YOUR_API_KEY\"\n",[90,21794,21795,21800],{"__ignoreMap":108},[112,21796,21797],{"class":114,"line":115},[112,21798,21799],{"class":118},"# 例：証明書の有効期限を確認するスクリプト\n",[112,21801,21802,21804,21806,21808],{"class":114,"line":122},[112,21803,14330],{"class":163},[112,21805,14463],{"class":156},[112,21807,1961],{"class":136},[112,21809,21810],{"class":136}," \"https:\u002F\u002Fapi.zerossl.com\u002Fcertificates?access_key=YOUR_API_KEY\"\n",[11,21812,21813],{"id":21813},"注意点とデメリット",[83,21815,21816],{"id":21816},"証明書の有効期間",[31,21818,21819,21822,21825],{},[34,21820,21821],{},"無料プランは90日間のみ",[34,21823,21824],{},"有料プランでは1年間の証明書を発行可能",[34,21826,21827],{},"更新を忘れると証明書が失効し、サイトにアクセスできなくなる",[83,21829,21830],{"id":21830},"管理の手間",[31,21832,21833,21836],{},[34,21834,21835],{},"自動更新機能がないため、手動更新が必要",[34,21837,21838],{},"複数ドメインがある場合、個別に管理が必要",[83,21840,21841],{"id":21841},"推奨される運用",[31,21843,21844,21847,21850],{},[34,21845,21846],{},"カレンダーに更新日をリマインダー設定",[34,21848,21849],{},"証明書の有効期限を監視するスクリプトを作成",[34,21851,21852],{},"本番環境では、証明書の自動更新に対応したサービスの検討も推奨",[11,21854,919],{"id":919},[15,21856,21857],{},"ZeroSSLを使った無料SSL証明書の発行とGCPへの適用方法を解説しました。",[15,21859,21860],{},[27,21861,21862],{},"メリット：",[31,21864,21865,21868,21871],{},[34,21866,21867],{},"無料でSSL証明書を取得可能",[34,21869,21870],{},"Webブラウザで簡単に管理",[34,21872,21873],{},"Let's Encryptの代替選択肢",[15,21875,21876],{},[27,21877,21878],{},"デメリット：",[31,21880,21881,21884],{},[34,21882,21883],{},"90日ごとの手動更新が必要",[34,21885,21886],{},"自動更新機能がない",[15,21888,21889],{},"小規模プロジェクトや検証環境には最適ですが、本番環境で長期運用する場合は、自動更新に対応したサービスやマネージドSSL証明書の利用も検討してください。",[944,21891,21892],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":108,"searchDepth":122,"depth":122,"links":21894},[21895,21896,21900,21901,21909,21914,21918,21923],{"id":13,"depth":122,"text":13},{"id":21416,"depth":122,"text":21417,"children":21897},[21898,21899],{"id":21423,"depth":143,"text":21424},{"id":2608,"depth":143,"text":2608},{"id":7094,"depth":122,"text":7094},{"id":21477,"depth":122,"text":21478,"children":21902},[21903,21904,21905,21906,21907,21908],{"id":21481,"depth":143,"text":21482},{"id":21492,"depth":143,"text":21493},{"id":21510,"depth":143,"text":21511},{"id":21521,"depth":143,"text":21522},{"id":21532,"depth":143,"text":21533},{"id":21603,"depth":143,"text":21604},{"id":21630,"depth":122,"text":21631,"children":21910},[21911,21912,21913],{"id":21634,"depth":143,"text":21635},{"id":21712,"depth":143,"text":21713},{"id":21746,"depth":143,"text":21747},{"id":21757,"depth":122,"text":21757,"children":21915},[21916,21917],{"id":21763,"depth":143,"text":21763},{"id":21780,"depth":143,"text":21780},{"id":21813,"depth":122,"text":21813,"children":21919},[21920,21921,21922],{"id":21816,"depth":143,"text":21816},{"id":21830,"depth":143,"text":21830},{"id":21841,"depth":143,"text":21841},{"id":919,"depth":122,"text":919},"2022-01-30","Let's Encryptの代替として注目されるZeroSSLを使った無料SSL証明書の取得方法を解説します。GCP Cloud Load BalancingへのSSL証明書適用手順も紹介します。",{"tags":21927},[21392,9603,21928,21929],"security","cloud","\u002Fblog\u002Fzerossl-free-ssl-certificate-setup",{"title":21401,"description":21925},"blog\u002Fzerossl-free-ssl-certificate-setup","OWJEnaU7PmVAit-yCMtK6fP_B1CeAtSsj7HwuD2Sjrc",{"id":21935,"title":21936,"body":21937,"date":22288,"description":22289,"extension":962,"meta":22290,"navigation":146,"path":22294,"seo":22295,"stem":22296,"__hash__":22297},"blog\u002Fblog\u002Fnginx-path-redirect.md","Nginxで特定パスをリダイレクトする設定",{"type":8,"value":21938,"toc":22276},[21939,21942,21945,21948,21959,21980,21983,21997,22000,22003,22191,22194,22197,22212,22226,22229,22238,22241,22263,22265,22273],[11,21940,21941],{"id":21941},"概要",[15,21943,21944],{},"Nginxで特定のパスへのアクセスを別のURLにリダイレクトさせる方法をまとめます。",[11,21946,21947],{"id":21947},"基本的なリダイレクト設定",[15,21949,21950,21951,21954,21955,21958],{},"特定のパスへのアクセスを301リダイレクトさせるには、",[90,21952,21953],{},"location","ブロックで",[90,21956,21957],{},"return","ディレクティブを使用します。",[103,21960,21964],{"className":21961,"code":21962,"language":21963,"meta":108,"style":108},"language-nginx shiki shiki-themes github-light github-dark","location \u002Fold-path\u002Findex.php {\n    return 301 https:\u002F\u002Fexample.com\u002Fnew-path;\n}\n","nginx",[90,21965,21966,21971,21976],{"__ignoreMap":108},[112,21967,21968],{"class":114,"line":115},[112,21969,21970],{},"location \u002Fold-path\u002Findex.php {\n",[112,21972,21973],{"class":114,"line":122},[112,21974,21975],{},"    return 301 https:\u002F\u002Fexample.com\u002Fnew-path;\n",[112,21977,21978],{"class":114,"line":143},[112,21979,1452],{},[83,21981,21982],{"id":21982},"リダイレクトの種類",[31,21984,21985,21991],{},[34,21986,21987,21990],{},[27,21988,21989],{},"301"," 恒久的なリダイレクト（SEO評価が引き継がれる）",[34,21992,21993,21996],{},[27,21994,21995],{},"302"," 一時的なリダイレクト",[11,21998,21999],{"id":21999},"実践的な設定例",[15,22001,22002],{},"Laravel\u002FPHPアプリケーション向けのNginx設定例です。",[103,22004,22006],{"className":21961,"code":22005,"language":21963,"meta":108,"style":108},"server {\n    listen 80;\n    server_name example.com;\n    root \u002Fvar\u002Fwww\u002Fhtml\u002Fpublic;\n    index index.php;\n    charset utf-8;\n\n    # セキュリティヘッダー\n    add_header X-Frame-Options \"SAMEORIGIN\";\n    add_header X-Content-Type-Options \"nosniff\";\n\n    # メインのルーティング\n    location \u002F {\n        try_files $uri $uri\u002F \u002Findex.php?$query_string;\n    }\n\n    # 特定パスのリダイレクト\n    location \u002Fold-path\u002Findex.php {\n        return 301 https:\u002F\u002Fexample.com\u002Fnew-path;\n    }\n\n    # PHP-FPMの設定\n    location ~ \\.php$ {\n        fastcgi_pass app:9000;\n        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n        include fastcgi_params;\n    }\n\n    # 静的ファイルのログを無効化\n    location = \u002Ffavicon.ico { access_log off; log_not_found off; }\n    location = \u002Frobots.txt  { access_log off; log_not_found off; }\n\n    # 隠しファイルへのアクセス拒否\n    location ~ \u002F\\.(?!well-known).* {\n        deny all;\n    }\n\n    error_page 404 \u002Findex.php;\n}\n",[90,22007,22008,22013,22018,22023,22028,22033,22038,22042,22047,22052,22057,22061,22066,22071,22076,22080,22084,22089,22094,22099,22103,22107,22112,22117,22122,22127,22132,22136,22140,22145,22150,22155,22159,22164,22169,22174,22178,22182,22187],{"__ignoreMap":108},[112,22009,22010],{"class":114,"line":115},[112,22011,22012],{},"server {\n",[112,22014,22015],{"class":114,"line":122},[112,22016,22017],{},"    listen 80;\n",[112,22019,22020],{"class":114,"line":143},[112,22021,22022],{},"    server_name example.com;\n",[112,22024,22025],{"class":114,"line":150},[112,22026,22027],{},"    root \u002Fvar\u002Fwww\u002Fhtml\u002Fpublic;\n",[112,22029,22030],{"class":114,"line":170},[112,22031,22032],{},"    index index.php;\n",[112,22034,22035],{"class":114,"line":182},[112,22036,22037],{},"    charset utf-8;\n",[112,22039,22040],{"class":114,"line":193},[112,22041,147],{"emptyLinePlaceholder":146},[112,22043,22044],{"class":114,"line":205},[112,22045,22046],{},"    # セキュリティヘッダー\n",[112,22048,22049],{"class":114,"line":241},[112,22050,22051],{},"    add_header X-Frame-Options \"SAMEORIGIN\";\n",[112,22053,22054],{"class":114,"line":247},[112,22055,22056],{},"    add_header X-Content-Type-Options \"nosniff\";\n",[112,22058,22059],{"class":114,"line":351},[112,22060,147],{"emptyLinePlaceholder":146},[112,22062,22063],{"class":114,"line":361},[112,22064,22065],{},"    # メインのルーティング\n",[112,22067,22068],{"class":114,"line":367},[112,22069,22070],{},"    location \u002F {\n",[112,22072,22073],{"class":114,"line":373},[112,22074,22075],{},"        try_files $uri $uri\u002F \u002Findex.php?$query_string;\n",[112,22077,22078],{"class":114,"line":379},[112,22079,3118],{},[112,22081,22082],{"class":114,"line":1302},[112,22083,147],{"emptyLinePlaceholder":146},[112,22085,22086],{"class":114,"line":1502},[112,22087,22088],{},"    # 特定パスのリダイレクト\n",[112,22090,22091],{"class":114,"line":1507},[112,22092,22093],{},"    location \u002Fold-path\u002Findex.php {\n",[112,22095,22096],{"class":114,"line":1512},[112,22097,22098],{},"        return 301 https:\u002F\u002Fexample.com\u002Fnew-path;\n",[112,22100,22101],{"class":114,"line":1518},[112,22102,3118],{},[112,22104,22105],{"class":114,"line":1524},[112,22106,147],{"emptyLinePlaceholder":146},[112,22108,22109],{"class":114,"line":1530},[112,22110,22111],{},"    # PHP-FPMの設定\n",[112,22113,22114],{"class":114,"line":1536},[112,22115,22116],{},"    location ~ \\.php$ {\n",[112,22118,22119],{"class":114,"line":1542},[112,22120,22121],{},"        fastcgi_pass app:9000;\n",[112,22123,22124],{"class":114,"line":2402},[112,22125,22126],{},"        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n",[112,22128,22129],{"class":114,"line":2407},[112,22130,22131],{},"        include fastcgi_params;\n",[112,22133,22134],{"class":114,"line":2413},[112,22135,3118],{},[112,22137,22138],{"class":114,"line":2446},[112,22139,147],{"emptyLinePlaceholder":146},[112,22141,22142],{"class":114,"line":2451},[112,22143,22144],{},"    # 静的ファイルのログを無効化\n",[112,22146,22147],{"class":114,"line":2464},[112,22148,22149],{},"    location = \u002Ffavicon.ico { access_log off; log_not_found off; }\n",[112,22151,22152],{"class":114,"line":2481},[112,22153,22154],{},"    location = \u002Frobots.txt  { access_log off; log_not_found off; }\n",[112,22156,22157],{"class":114,"line":2486},[112,22158,147],{"emptyLinePlaceholder":146},[112,22160,22161],{"class":114,"line":3229},[112,22162,22163],{},"    # 隠しファイルへのアクセス拒否\n",[112,22165,22166],{"class":114,"line":3235},[112,22167,22168],{},"    location ~ \u002F\\.(?!well-known).* {\n",[112,22170,22171],{"class":114,"line":3657},[112,22172,22173],{},"        deny all;\n",[112,22175,22176],{"class":114,"line":3662},[112,22177,3118],{},[112,22179,22180],{"class":114,"line":3667},[112,22181,147],{"emptyLinePlaceholder":146},[112,22183,22184],{"class":114,"line":3680},[112,22185,22186],{},"    error_page 404 \u002Findex.php;\n",[112,22188,22189],{"class":114,"line":3692},[112,22190,1452],{},[11,22192,22193],{"id":22193},"主要な設定項目の説明",[83,22195,22196],{"id":22196},"セキュリティヘッダー",[103,22198,22200],{"className":21961,"code":22199,"language":21963,"meta":108,"style":108},"add_header X-Frame-Options \"SAMEORIGIN\";\nadd_header X-Content-Type-Options \"nosniff\";\n",[90,22201,22202,22207],{"__ignoreMap":108},[112,22203,22204],{"class":114,"line":115},[112,22205,22206],{},"add_header X-Frame-Options \"SAMEORIGIN\";\n",[112,22208,22209],{"class":114,"line":122},[112,22210,22211],{},"add_header X-Content-Type-Options \"nosniff\";\n",[31,22213,22214,22220],{},[34,22215,22216,22219],{},[27,22217,22218],{},"X-Frame-Options"," クリックジャッキング攻撃を防止",[34,22221,22222,22225],{},[27,22223,22224],{},"X-Content-Type-Options"," MIMEタイプスニッフィングを防止",[83,22227,22228],{"id":22228},"try_filesディレクティブ",[103,22230,22232],{"className":21961,"code":22231,"language":21963,"meta":108,"style":108},"try_files $uri $uri\u002F \u002Findex.php?$query_string;\n",[90,22233,22234],{"__ignoreMap":108},[112,22235,22236],{"class":114,"line":115},[112,22237,22231],{},[15,22239,22240],{},"ファイルの存在を順に確認し、存在しない場合はPHPルーターに処理を委譲します。",[4108,22242,22243,22250,22256],{},[34,22244,22245,22246,22249],{},"静的ファイルとして ",[90,22247,22248],{},"$uri"," を探す",[34,22251,22252,22253,22249],{},"ディレクトリとして ",[90,22254,22255],{},"$uri\u002F",[34,22257,22258,22259,22262],{},"どちらも存在しなければ ",[90,22260,22261],{},"\u002Findex.php"," にフォールバック",[11,22264,919],{"id":919},[15,22266,22267,22268,21666,22270,22272],{},"Nginxでのリダイレクト設定は",[90,22269,21953],{},[90,22271,21957],{},"を組み合わせることでシンプルに実装できます。セキュリティヘッダーも合わせて設定することで、より安全なWebサーバーを構築できます。",[944,22274,22275],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":108,"searchDepth":122,"depth":122,"links":22277},[22278,22279,22282,22283,22287],{"id":21941,"depth":122,"text":21941},{"id":21947,"depth":122,"text":21947,"children":22280},[22281],{"id":21982,"depth":143,"text":21982},{"id":21999,"depth":122,"text":21999},{"id":22193,"depth":122,"text":22193,"children":22284},[22285,22286],{"id":22196,"depth":143,"text":22196},{"id":22228,"depth":143,"text":22228},{"id":919,"depth":122,"text":919},"2022-01-15","Nginxで特定のパスにアクセスされた際に別のURLへリダイレクトさせる方法と、セキュリティヘッダーの設定について解説します。",{"tags":22291},[21963,22292,22293],"webserver","redirect","\u002Fblog\u002Fnginx-path-redirect",{"title":21936,"description":22289},"blog\u002Fnginx-path-redirect","Vfn7JmUZcLljzRFRJfydrZuvQP8vzAG7b0SEQvrqw5M",{"id":22299,"title":22300,"body":22301,"date":23657,"description":23658,"extension":962,"meta":23659,"navigation":146,"path":23663,"seo":23664,"stem":23665,"__hash__":23666},"blog\u002Fblog\u002Flaravel-mysql-fulltext-search.md","LaravelでMySQL全文検索を実装する",{"type":8,"value":22302,"toc":23632},[22303,22307,22313,22330,22333,22337,22344,22347,22353,22356,22359,22363,22385,22389,22463,22467,22470,22700,22703,22707,22716,22733,22737,22746,22757,22761,22791,22799,22803,22899,22925,22929,23081,23084,23088,23097,23117,23121,23164,23168,23446,23449,23452,23462,23489,23492,23511,23515,23519,23534,23547,23550,23565,23577,23579,23582,23602,23605,23607,23629],[11,22304,22306],{"id":22305},"like検索の課題","LIKE検索の課題",[15,22308,22309,22312],{},[90,22310,22311],{},"LIKE '%keyword%'"," を使った部分一致検索は、データ量が増えるとパフォーマンスが低下します。",[103,22314,22318],{"className":22315,"code":22316,"language":22317,"meta":108,"style":108},"language-sql shiki shiki-themes github-light github-dark","-- インデックスが使えない\nSELECT * FROM shops WHERE name LIKE '%太郎%'\n","sql",[90,22319,22320,22325],{"__ignoreMap":108},[112,22321,22322],{"class":114,"line":115},[112,22323,22324],{},"-- インデックスが使えない\n",[112,22326,22327],{"class":114,"line":122},[112,22328,22329],{},"SELECT * FROM shops WHERE name LIKE '%太郎%'\n",[15,22331,22332],{},"MySQLの**全文検索インデックス（Fulltext Index）**を使うことで、この問題を解決できます。",[11,22334,22336],{"id":22335},"n-gramパーサーとは","N-gramパーサーとは",[15,22338,22339,22340,22343],{},"MySQLの全文検索では、",[27,22341,22342],{},"N-gramパーサー","を使用することで日本語にも対応できます。",[15,22345,22346],{},"N-gramは文字列をN文字ずつに分割してインデックスを作成する方法です。",[103,22348,22351],{"className":22349,"code":22350,"language":5951},[5949],"\"サンプル太郎\" → [\"サン\", \"ンプ\", \"プル\", \"ル太\", \"太郎\"]（2-gram\u002FBigram）\n",[90,22352,22350],{"__ignoreMap":108},[15,22354,22355],{},"これにより、部分一致検索でもインデックスを活用できます。",[11,22357,22358],{"id":22358},"実装手順",[83,22360,22362],{"id":22361},"_1-modelとmigrationを生成","1. Modelとmigrationを生成",[103,22364,22366],{"className":771,"code":22365,"language":773,"meta":108,"style":108},"php artisan make:model Shop --migration\n",[90,22367,22368],{"__ignoreMap":108},[112,22369,22370,22373,22376,22379,22382],{"class":114,"line":115},[112,22371,22372],{"class":163},"php",[112,22374,22375],{"class":136}," artisan",[112,22377,22378],{"class":136}," make:model",[112,22380,22381],{"class":136}," Shop",[112,22383,22384],{"class":156}," --migration\n",[83,22386,22388],{"id":22387},"_2-modelを定義","2. Modelを定義",[103,22390,22393],{"className":22391,"code":22392,"language":22372,"meta":108,"style":108},"language-php shiki shiki-themes github-light github-dark","\u003C?php\n\nnamespace App\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Shop extends Model\n{\n    use HasFactory;\n\n    protected $table = 'shops';\n\n    protected $fillable = ['name', 'age', 'gender_id'];\n}\n",[90,22394,22395,22400,22404,22409,22413,22418,22423,22427,22432,22436,22441,22445,22450,22454,22459],{"__ignoreMap":108},[112,22396,22397],{"class":114,"line":115},[112,22398,22399],{},"\u003C?php\n",[112,22401,22402],{"class":114,"line":122},[112,22403,147],{"emptyLinePlaceholder":146},[112,22405,22406],{"class":114,"line":143},[112,22407,22408],{},"namespace App\\Models;\n",[112,22410,22411],{"class":114,"line":150},[112,22412,147],{"emptyLinePlaceholder":146},[112,22414,22415],{"class":114,"line":170},[112,22416,22417],{},"use Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\n",[112,22419,22420],{"class":114,"line":182},[112,22421,22422],{},"use Illuminate\\Database\\Eloquent\\Model;\n",[112,22424,22425],{"class":114,"line":193},[112,22426,147],{"emptyLinePlaceholder":146},[112,22428,22429],{"class":114,"line":205},[112,22430,22431],{},"class Shop extends Model\n",[112,22433,22434],{"class":114,"line":241},[112,22435,2965],{},[112,22437,22438],{"class":114,"line":247},[112,22439,22440],{},"    use HasFactory;\n",[112,22442,22443],{"class":114,"line":351},[112,22444,147],{"emptyLinePlaceholder":146},[112,22446,22447],{"class":114,"line":361},[112,22448,22449],{},"    protected $table = 'shops';\n",[112,22451,22452],{"class":114,"line":367},[112,22453,147],{"emptyLinePlaceholder":146},[112,22455,22456],{"class":114,"line":373},[112,22457,22458],{},"    protected $fillable = ['name', 'age', 'gender_id'];\n",[112,22460,22461],{"class":114,"line":379},[112,22462,1452],{},[83,22464,22466],{"id":22465},"_3-全文検索用のmigrationを作成","3. 全文検索用のmigrationを作成",[15,22468,22469],{},"仮想カラムと全文検索インデックスを定義します。",[103,22471,22473],{"className":22391,"code":22472,"language":22372,"meta":108,"style":108},"\u003C?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Facades\\Schema;\n\nclass CreateShopsTable extends Migration\n{\n    public function up()\n    {\n        Schema::create('shops', function (Blueprint $table) {\n            $table->bigIncrements('id')->comment('店舗ID');\n            $table->string('name', 255)->comment('店舗名');\n            $table->unsignedInteger('age')->comment('設立年数');\n            $table->smallInteger('gender_id')->comment('対象性別');\n            $table->timestamps();\n        });\n\n        \u002F\u002F 検索用の仮想カラムを追加\n        DB::statement(\"\n            ALTER TABLE shops\n            ADD free_word TEXT AS (\n                CONCAT(\n                    IFNULL(age, ''), ' ',\n                    IFNULL(name, ''), ' ',\n                    CASE gender_id\n                        WHEN 1 THEN '男性'\n                        WHEN 2 THEN '女性'\n                        ELSE ''\n                    END\n                )\n            ) STORED\n        \");\n\n        \u002F\u002F N-gram全文検索インデックスを作成\n        DB::statement(\"\n            ALTER TABLE shops\n            ADD FULLTEXT INDEX ftx_free_word (free_word)\n            WITH PARSER ngram\n        \");\n    }\n\n    public function down()\n    {\n        Schema::dropIfExists('shops');\n    }\n}\n",[90,22474,22475,22479,22483,22488,22493,22498,22503,22507,22512,22516,22521,22525,22530,22535,22540,22545,22550,22555,22560,22564,22569,22574,22579,22584,22589,22594,22599,22604,22609,22614,22619,22624,22629,22634,22639,22643,22648,22652,22656,22661,22666,22670,22674,22678,22683,22687,22692,22696],{"__ignoreMap":108},[112,22476,22477],{"class":114,"line":115},[112,22478,22399],{},[112,22480,22481],{"class":114,"line":122},[112,22482,147],{"emptyLinePlaceholder":146},[112,22484,22485],{"class":114,"line":143},[112,22486,22487],{},"use Illuminate\\Database\\Migrations\\Migration;\n",[112,22489,22490],{"class":114,"line":150},[112,22491,22492],{},"use Illuminate\\Database\\Schema\\Blueprint;\n",[112,22494,22495],{"class":114,"line":170},[112,22496,22497],{},"use Illuminate\\Support\\Facades\\DB;\n",[112,22499,22500],{"class":114,"line":182},[112,22501,22502],{},"use Illuminate\\Support\\Facades\\Schema;\n",[112,22504,22505],{"class":114,"line":193},[112,22506,147],{"emptyLinePlaceholder":146},[112,22508,22509],{"class":114,"line":205},[112,22510,22511],{},"class CreateShopsTable extends Migration\n",[112,22513,22514],{"class":114,"line":241},[112,22515,2965],{},[112,22517,22518],{"class":114,"line":247},[112,22519,22520],{},"    public function up()\n",[112,22522,22523],{"class":114,"line":351},[112,22524,2309],{},[112,22526,22527],{"class":114,"line":361},[112,22528,22529],{},"        Schema::create('shops', function (Blueprint $table) {\n",[112,22531,22532],{"class":114,"line":367},[112,22533,22534],{},"            $table->bigIncrements('id')->comment('店舗ID');\n",[112,22536,22537],{"class":114,"line":373},[112,22538,22539],{},"            $table->string('name', 255)->comment('店舗名');\n",[112,22541,22542],{"class":114,"line":379},[112,22543,22544],{},"            $table->unsignedInteger('age')->comment('設立年数');\n",[112,22546,22547],{"class":114,"line":1302},[112,22548,22549],{},"            $table->smallInteger('gender_id')->comment('対象性別');\n",[112,22551,22552],{"class":114,"line":1502},[112,22553,22554],{},"            $table->timestamps();\n",[112,22556,22557],{"class":114,"line":1507},[112,22558,22559],{},"        });\n",[112,22561,22562],{"class":114,"line":1512},[112,22563,147],{"emptyLinePlaceholder":146},[112,22565,22566],{"class":114,"line":1518},[112,22567,22568],{},"        \u002F\u002F 検索用の仮想カラムを追加\n",[112,22570,22571],{"class":114,"line":1524},[112,22572,22573],{},"        DB::statement(\"\n",[112,22575,22576],{"class":114,"line":1530},[112,22577,22578],{},"            ALTER TABLE shops\n",[112,22580,22581],{"class":114,"line":1536},[112,22582,22583],{},"            ADD free_word TEXT AS (\n",[112,22585,22586],{"class":114,"line":1542},[112,22587,22588],{},"                CONCAT(\n",[112,22590,22591],{"class":114,"line":2402},[112,22592,22593],{},"                    IFNULL(age, ''), ' ',\n",[112,22595,22596],{"class":114,"line":2407},[112,22597,22598],{},"                    IFNULL(name, ''), ' ',\n",[112,22600,22601],{"class":114,"line":2413},[112,22602,22603],{},"                    CASE gender_id\n",[112,22605,22606],{"class":114,"line":2446},[112,22607,22608],{},"                        WHEN 1 THEN '男性'\n",[112,22610,22611],{"class":114,"line":2451},[112,22612,22613],{},"                        WHEN 2 THEN '女性'\n",[112,22615,22616],{"class":114,"line":2464},[112,22617,22618],{},"                        ELSE ''\n",[112,22620,22621],{"class":114,"line":2481},[112,22622,22623],{},"                    END\n",[112,22625,22626],{"class":114,"line":2486},[112,22627,22628],{},"                )\n",[112,22630,22631],{"class":114,"line":3229},[112,22632,22633],{},"            ) STORED\n",[112,22635,22636],{"class":114,"line":3235},[112,22637,22638],{},"        \");\n",[112,22640,22641],{"class":114,"line":3657},[112,22642,147],{"emptyLinePlaceholder":146},[112,22644,22645],{"class":114,"line":3662},[112,22646,22647],{},"        \u002F\u002F N-gram全文検索インデックスを作成\n",[112,22649,22650],{"class":114,"line":3667},[112,22651,22573],{},[112,22653,22654],{"class":114,"line":3680},[112,22655,22578],{},[112,22657,22658],{"class":114,"line":3692},[112,22659,22660],{},"            ADD FULLTEXT INDEX ftx_free_word (free_word)\n",[112,22662,22663],{"class":114,"line":3716},[112,22664,22665],{},"            WITH PARSER ngram\n",[112,22667,22668],{"class":114,"line":3737},[112,22669,22638],{},[112,22671,22672],{"class":114,"line":3742},[112,22673,3118],{},[112,22675,22676],{"class":114,"line":3747},[112,22677,147],{"emptyLinePlaceholder":146},[112,22679,22680],{"class":114,"line":3752},[112,22681,22682],{},"    public function down()\n",[112,22684,22685],{"class":114,"line":3758},[112,22686,2309],{},[112,22688,22689],{"class":114,"line":3778},[112,22690,22691],{},"        Schema::dropIfExists('shops');\n",[112,22693,22694],{"class":114,"line":3787},[112,22695,3118],{},[112,22697,22698],{"class":114,"line":3809},[112,22699,1452],{},[83,22701,22702],{"id":22702},"migrationの解説",[8992,22704,22706],{"id":22705},"仮想カラムgenerated-column","仮想カラム（Generated Column）",[103,22708,22710],{"className":22315,"code":22709,"language":22317,"meta":108,"style":108},"ALTER TABLE shops ADD free_word TEXT AS (...) STORED\n",[90,22711,22712],{"__ignoreMap":108},[112,22713,22714],{"class":114,"line":115},[112,22715,22709],{},[31,22717,22718,22724,22730],{},[34,22719,22720,22723],{},[27,22721,22722],{},"仮想カラム"," 他のカラムから自動生成されるカラム",[34,22725,22726,22729],{},[27,22727,22728],{},"STORED"," 物理的に保存される（検索インデックスを作成可能）",[34,22731,22732],{},"複数のカラムを結合して検索対象を作成",[8992,22734,22736],{"id":22735},"concat関数","CONCAT関数",[103,22738,22740],{"className":22315,"code":22739,"language":22317,"meta":108,"style":108},"CONCAT(IFNULL(age, ''), ' ', IFNULL(name, ''), ' ', ...)\n",[90,22741,22742],{"__ignoreMap":108},[112,22743,22744],{"class":114,"line":115},[112,22745,22739],{},[31,22747,22748,22751],{},[34,22749,22750],{},"複数の文字列を連結",[34,22752,22753,22756],{},[90,22754,22755],{},"IFNULL",": NULL値を空文字列に変換",[8992,22758,22760],{"id":22759},"case式","CASE式",[103,22762,22764],{"className":22315,"code":22763,"language":22317,"meta":108,"style":108},"CASE gender_id\n    WHEN 1 THEN '男性'\n    WHEN 2 THEN '女性'\n    ELSE ''\nEND\n",[90,22765,22766,22771,22776,22781,22786],{"__ignoreMap":108},[112,22767,22768],{"class":114,"line":115},[112,22769,22770],{},"CASE gender_id\n",[112,22772,22773],{"class":114,"line":122},[112,22774,22775],{},"    WHEN 1 THEN '男性'\n",[112,22777,22778],{"class":114,"line":143},[112,22779,22780],{},"    WHEN 2 THEN '女性'\n",[112,22782,22783],{"class":114,"line":150},[112,22784,22785],{},"    ELSE ''\n",[112,22787,22788],{"class":114,"line":170},[112,22789,22790],{},"END\n",[31,22792,22793,22796],{},[34,22794,22795],{},"IDを日本語の名称に変換",[34,22797,22798],{},"日本語でも検索可能にする",[83,22800,22802],{"id":22801},"_4-seederでテストデータを作成","4. Seederでテストデータを作成",[103,22804,22806],{"className":22391,"code":22805,"language":22372,"meta":108,"style":108},"\u003C?php\n\nnamespace Database\\Seeders;\n\nuse App\\Models\\Shop;\nuse Illuminate\\Database\\Seeder;\n\nclass DummyShopsSeeder extends Seeder\n{\n    public function run()\n    {\n        $data = [\n            ['name' => 'サンプル太郎', 'age' => 25, 'gender_id' => 1],\n            ['name' => 'サンプル花子', 'age' => 30, 'gender_id' => 2],\n            ['name' => 'サンプル二郎', 'age' => 20, 'gender_id' => 1],\n        ];\n\n        Shop::query()->insert($data);\n    }\n}\n",[90,22807,22808,22812,22816,22821,22825,22830,22835,22839,22844,22848,22853,22857,22862,22867,22872,22877,22882,22886,22891,22895],{"__ignoreMap":108},[112,22809,22810],{"class":114,"line":115},[112,22811,22399],{},[112,22813,22814],{"class":114,"line":122},[112,22815,147],{"emptyLinePlaceholder":146},[112,22817,22818],{"class":114,"line":143},[112,22819,22820],{},"namespace Database\\Seeders;\n",[112,22822,22823],{"class":114,"line":150},[112,22824,147],{"emptyLinePlaceholder":146},[112,22826,22827],{"class":114,"line":170},[112,22828,22829],{},"use App\\Models\\Shop;\n",[112,22831,22832],{"class":114,"line":182},[112,22833,22834],{},"use Illuminate\\Database\\Seeder;\n",[112,22836,22837],{"class":114,"line":193},[112,22838,147],{"emptyLinePlaceholder":146},[112,22840,22841],{"class":114,"line":205},[112,22842,22843],{},"class DummyShopsSeeder extends Seeder\n",[112,22845,22846],{"class":114,"line":241},[112,22847,2965],{},[112,22849,22850],{"class":114,"line":247},[112,22851,22852],{},"    public function run()\n",[112,22854,22855],{"class":114,"line":351},[112,22856,2309],{},[112,22858,22859],{"class":114,"line":361},[112,22860,22861],{},"        $data = [\n",[112,22863,22864],{"class":114,"line":367},[112,22865,22866],{},"            ['name' => 'サンプル太郎', 'age' => 25, 'gender_id' => 1],\n",[112,22868,22869],{"class":114,"line":373},[112,22870,22871],{},"            ['name' => 'サンプル花子', 'age' => 30, 'gender_id' => 2],\n",[112,22873,22874],{"class":114,"line":379},[112,22875,22876],{},"            ['name' => 'サンプル二郎', 'age' => 20, 'gender_id' => 1],\n",[112,22878,22879],{"class":114,"line":1302},[112,22880,22881],{},"        ];\n",[112,22883,22884],{"class":114,"line":1502},[112,22885,147],{"emptyLinePlaceholder":146},[112,22887,22888],{"class":114,"line":1507},[112,22889,22890],{},"        Shop::query()->insert($data);\n",[112,22892,22893],{"class":114,"line":1512},[112,22894,3118],{},[112,22896,22897],{"class":114,"line":1518},[112,22898,1452],{},[103,22900,22902],{"className":771,"code":22901,"language":773,"meta":108,"style":108},"php artisan migrate\nphp artisan db:seed --class=DummyShopsSeeder\n",[90,22903,22904,22913],{"__ignoreMap":108},[112,22905,22906,22908,22910],{"class":114,"line":115},[112,22907,22372],{"class":163},[112,22909,22375],{"class":136},[112,22911,22912],{"class":136}," migrate\n",[112,22914,22915,22917,22919,22922],{"class":114,"line":122},[112,22916,22372],{"class":163},[112,22918,22375],{"class":136},[112,22920,22921],{"class":136}," db:seed",[112,22923,22924],{"class":156}," --class=DummyShopsSeeder\n",[83,22926,22928],{"id":22927},"_5-controllerで全文検索を実装","5. Controllerで全文検索を実装",[103,22930,22932],{"className":22391,"code":22931,"language":22372,"meta":108,"style":108},"\u003C?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Models\\Shop;\nuse Illuminate\\Http\\Request;\n\nclass ShopController extends Controller\n{\n    public function index(Request $request)\n    {\n        $query = Shop::query();\n        $freeWord = $request->input('free_word');\n\n        if ($freeWord) {\n            \u002F\u002F 全文検索を実行\n            $query->whereRaw(\n                \"MATCH(free_word) AGAINST (? IN BOOLEAN MODE)\",\n                [$freeWord]\n            );\n        }\n\n        $shops = $query\n            ->select(['id', 'name', 'age', 'gender_id'])\n            ->paginate(20);\n\n        return view('index', [\n            'shops' => $shops,\n            'parameters' => $request->all(),\n        ]);\n    }\n}\n",[90,22933,22934,22938,22942,22947,22951,22955,22960,22964,22969,22973,22978,22982,22987,22992,22996,23001,23006,23011,23016,23021,23026,23030,23034,23039,23044,23049,23053,23058,23063,23068,23073,23077],{"__ignoreMap":108},[112,22935,22936],{"class":114,"line":115},[112,22937,22399],{},[112,22939,22940],{"class":114,"line":122},[112,22941,147],{"emptyLinePlaceholder":146},[112,22943,22944],{"class":114,"line":143},[112,22945,22946],{},"namespace App\\Http\\Controllers;\n",[112,22948,22949],{"class":114,"line":150},[112,22950,147],{"emptyLinePlaceholder":146},[112,22952,22953],{"class":114,"line":170},[112,22954,22829],{},[112,22956,22957],{"class":114,"line":182},[112,22958,22959],{},"use Illuminate\\Http\\Request;\n",[112,22961,22962],{"class":114,"line":193},[112,22963,147],{"emptyLinePlaceholder":146},[112,22965,22966],{"class":114,"line":205},[112,22967,22968],{},"class ShopController extends Controller\n",[112,22970,22971],{"class":114,"line":241},[112,22972,2965],{},[112,22974,22975],{"class":114,"line":247},[112,22976,22977],{},"    public function index(Request $request)\n",[112,22979,22980],{"class":114,"line":351},[112,22981,2309],{},[112,22983,22984],{"class":114,"line":361},[112,22985,22986],{},"        $query = Shop::query();\n",[112,22988,22989],{"class":114,"line":367},[112,22990,22991],{},"        $freeWord = $request->input('free_word');\n",[112,22993,22994],{"class":114,"line":373},[112,22995,147],{"emptyLinePlaceholder":146},[112,22997,22998],{"class":114,"line":379},[112,22999,23000],{},"        if ($freeWord) {\n",[112,23002,23003],{"class":114,"line":1302},[112,23004,23005],{},"            \u002F\u002F 全文検索を実行\n",[112,23007,23008],{"class":114,"line":1502},[112,23009,23010],{},"            $query->whereRaw(\n",[112,23012,23013],{"class":114,"line":1507},[112,23014,23015],{},"                \"MATCH(free_word) AGAINST (? IN BOOLEAN MODE)\",\n",[112,23017,23018],{"class":114,"line":1512},[112,23019,23020],{},"                [$freeWord]\n",[112,23022,23023],{"class":114,"line":1518},[112,23024,23025],{},"            );\n",[112,23027,23028],{"class":114,"line":1524},[112,23029,5481],{},[112,23031,23032],{"class":114,"line":1530},[112,23033,147],{"emptyLinePlaceholder":146},[112,23035,23036],{"class":114,"line":1536},[112,23037,23038],{},"        $shops = $query\n",[112,23040,23041],{"class":114,"line":1542},[112,23042,23043],{},"            ->select(['id', 'name', 'age', 'gender_id'])\n",[112,23045,23046],{"class":114,"line":2402},[112,23047,23048],{},"            ->paginate(20);\n",[112,23050,23051],{"class":114,"line":2407},[112,23052,147],{"emptyLinePlaceholder":146},[112,23054,23055],{"class":114,"line":2413},[112,23056,23057],{},"        return view('index', [\n",[112,23059,23060],{"class":114,"line":2446},[112,23061,23062],{},"            'shops' => $shops,\n",[112,23064,23065],{"class":114,"line":2451},[112,23066,23067],{},"            'parameters' => $request->all(),\n",[112,23069,23070],{"class":114,"line":2464},[112,23071,23072],{},"        ]);\n",[112,23074,23075],{"class":114,"line":2481},[112,23076,3118],{},[112,23078,23079],{"class":114,"line":2486},[112,23080,1452],{},[83,23082,23083],{"id":23083},"全文検索の解説",[8992,23085,23087],{"id":23086},"match-against構文","MATCH ... AGAINST構文",[103,23089,23091],{"className":22315,"code":23090,"language":22317,"meta":108,"style":108},"MATCH(free_word) AGAINST ('検索キーワード' IN BOOLEAN MODE)\n",[90,23092,23093],{"__ignoreMap":108},[112,23094,23095],{"class":114,"line":115},[112,23096,23090],{},[31,23098,23099,23105,23111],{},[34,23100,23101,23104],{},[27,23102,23103],{},"MATCH"," 全文検索インデックスを使用",[34,23106,23107,23110],{},[27,23108,23109],{},"AGAINST"," 検索キーワードを指定",[34,23112,23113,23116],{},[27,23114,23115],{},"BOOLEAN MODE"," 演算子を使った詳細な検索が可能",[8992,23118,23120],{"id":23119},"booleanモードの演算子","BOOLEANモードの演算子",[103,23122,23124],{"className":22391,"code":23123,"language":22372,"meta":108,"style":108},"\u002F\u002F AND検索\n$query->whereRaw(\"MATCH(free_word) AGAINST ('+太郎 +男性' IN BOOLEAN MODE)\");\n\n\u002F\u002F OR検索\n$query->whereRaw(\"MATCH(free_word) AGAINST ('太郎 花子' IN BOOLEAN MODE)\");\n\n\u002F\u002F NOT検索\n$query->whereRaw(\"MATCH(free_word) AGAINST ('+サンプル -花子' IN BOOLEAN MODE)\");\n",[90,23125,23126,23131,23136,23140,23145,23150,23154,23159],{"__ignoreMap":108},[112,23127,23128],{"class":114,"line":115},[112,23129,23130],{},"\u002F\u002F AND検索\n",[112,23132,23133],{"class":114,"line":122},[112,23134,23135],{},"$query->whereRaw(\"MATCH(free_word) AGAINST ('+太郎 +男性' IN BOOLEAN MODE)\");\n",[112,23137,23138],{"class":114,"line":143},[112,23139,147],{"emptyLinePlaceholder":146},[112,23141,23142],{"class":114,"line":150},[112,23143,23144],{},"\u002F\u002F OR検索\n",[112,23146,23147],{"class":114,"line":170},[112,23148,23149],{},"$query->whereRaw(\"MATCH(free_word) AGAINST ('太郎 花子' IN BOOLEAN MODE)\");\n",[112,23151,23152],{"class":114,"line":182},[112,23153,147],{"emptyLinePlaceholder":146},[112,23155,23156],{"class":114,"line":193},[112,23157,23158],{},"\u002F\u002F NOT検索\n",[112,23160,23161],{"class":114,"line":205},[112,23162,23163],{},"$query->whereRaw(\"MATCH(free_word) AGAINST ('+サンプル -花子' IN BOOLEAN MODE)\");\n",[83,23165,23167],{"id":23166},"_6-viewの実装","6. Viewの実装",[103,23169,23171],{"className":22391,"code":23170,"language":22372,"meta":108,"style":108},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"{{ str_replace('_', '-', app()->getLocale()) }}\">\n\u003Chead>\n    \u003Cmeta charset=\"utf-8\">\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    \u003Ctitle>全文検索デモ\u003C\u002Ftitle>\n    \u003Clink href=\"{{ asset('css\u002Fapp.css') }}\" rel=\"stylesheet\">\n\u003C\u002Fhead>\n\u003Cbody>\n\u003Cdiv class=\"container\">\n    \u003Cdiv class=\"card\">\n        \u003Cdiv class=\"card-header\">全文検索\u003C\u002Fdiv>\n        \u003Cdiv class=\"card-body\">\n            \u003Cform action=\"\u002Fadmin\" method=\"GET\">\n                \u003Cdiv class=\"mb-2\">\n                    \u003Clabel for=\"free_word\" class=\"form-label\">キーワード\u003C\u002Flabel>\n                    \u003Cinput type=\"text\"\n                           class=\"form-control\"\n                           name=\"free_word\"\n                           id=\"free_word\"\n                           value=\"{{ $parameters['free_word'] ?? '' }}\">\n                \u003C\u002Fdiv>\n                \u003Cbutton type=\"submit\" class=\"btn btn-primary\">検索\u003C\u002Fbutton>\n                \u003Ca href=\"\u002Fadmin\" class=\"btn btn-secondary\">クリア\u003C\u002Fa>\n            \u003C\u002Fform>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"mt-3\">\n        \u003Cp>検索結果: {{ $shops->total() }}件\u003C\u002Fp>\n        \u003Ctable class=\"table\">\n            \u003Cthead>\n                \u003Ctr>\n                    \u003Cth>ID\u003C\u002Fth>\n                    \u003Cth>名前\u003C\u002Fth>\n                    \u003Cth>年齢\u003C\u002Fth>\n                    \u003Cth>性別\u003C\u002Fth>\n                \u003C\u002Ftr>\n            \u003C\u002Fthead>\n            \u003Ctbody>\n                @foreach($shops as $shop)\n                    \u003Ctr>\n                        \u003Ctd>{{ $shop->id }}\u003C\u002Ftd>\n                        \u003Ctd>{{ $shop->name }}\u003C\u002Ftd>\n                        \u003Ctd>{{ $shop->age }}\u003C\u002Ftd>\n                        \u003Ctd>{{ $shop->gender_id === 1 ? '男性' : '女性' }}\u003C\u002Ftd>\n                    \u003C\u002Ftr>\n                @endforeach\n            \u003C\u002Ftbody>\n        \u003C\u002Ftable>\n        {{ $shops->links() }}\n    \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[90,23172,23173,23178,23183,23188,23193,23198,23203,23208,23213,23218,23223,23228,23233,23238,23243,23248,23253,23258,23263,23268,23273,23278,23283,23288,23293,23298,23303,23308,23312,23317,23322,23327,23332,23337,23342,23347,23352,23357,23362,23367,23372,23377,23382,23387,23392,23397,23402,23407,23412,23417,23422,23427,23431,23436,23441],{"__ignoreMap":108},[112,23174,23175],{"class":114,"line":115},[112,23176,23177],{},"\u003C!DOCTYPE html>\n",[112,23179,23180],{"class":114,"line":122},[112,23181,23182],{},"\u003Chtml lang=\"{{ str_replace('_', '-', app()->getLocale()) }}\">\n",[112,23184,23185],{"class":114,"line":143},[112,23186,23187],{},"\u003Chead>\n",[112,23189,23190],{"class":114,"line":150},[112,23191,23192],{},"    \u003Cmeta charset=\"utf-8\">\n",[112,23194,23195],{"class":114,"line":170},[112,23196,23197],{},"    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n",[112,23199,23200],{"class":114,"line":182},[112,23201,23202],{},"    \u003Ctitle>全文検索デモ\u003C\u002Ftitle>\n",[112,23204,23205],{"class":114,"line":193},[112,23206,23207],{},"    \u003Clink href=\"{{ asset('css\u002Fapp.css') }}\" rel=\"stylesheet\">\n",[112,23209,23210],{"class":114,"line":205},[112,23211,23212],{},"\u003C\u002Fhead>\n",[112,23214,23215],{"class":114,"line":241},[112,23216,23217],{},"\u003Cbody>\n",[112,23219,23220],{"class":114,"line":247},[112,23221,23222],{},"\u003Cdiv class=\"container\">\n",[112,23224,23225],{"class":114,"line":351},[112,23226,23227],{},"    \u003Cdiv class=\"card\">\n",[112,23229,23230],{"class":114,"line":361},[112,23231,23232],{},"        \u003Cdiv class=\"card-header\">全文検索\u003C\u002Fdiv>\n",[112,23234,23235],{"class":114,"line":367},[112,23236,23237],{},"        \u003Cdiv class=\"card-body\">\n",[112,23239,23240],{"class":114,"line":373},[112,23241,23242],{},"            \u003Cform action=\"\u002Fadmin\" method=\"GET\">\n",[112,23244,23245],{"class":114,"line":379},[112,23246,23247],{},"                \u003Cdiv class=\"mb-2\">\n",[112,23249,23250],{"class":114,"line":1302},[112,23251,23252],{},"                    \u003Clabel for=\"free_word\" class=\"form-label\">キーワード\u003C\u002Flabel>\n",[112,23254,23255],{"class":114,"line":1502},[112,23256,23257],{},"                    \u003Cinput type=\"text\"\n",[112,23259,23260],{"class":114,"line":1507},[112,23261,23262],{},"                           class=\"form-control\"\n",[112,23264,23265],{"class":114,"line":1512},[112,23266,23267],{},"                           name=\"free_word\"\n",[112,23269,23270],{"class":114,"line":1518},[112,23271,23272],{},"                           id=\"free_word\"\n",[112,23274,23275],{"class":114,"line":1524},[112,23276,23277],{},"                           value=\"{{ $parameters['free_word'] ?? '' }}\">\n",[112,23279,23280],{"class":114,"line":1530},[112,23281,23282],{},"                \u003C\u002Fdiv>\n",[112,23284,23285],{"class":114,"line":1536},[112,23286,23287],{},"                \u003Cbutton type=\"submit\" class=\"btn btn-primary\">検索\u003C\u002Fbutton>\n",[112,23289,23290],{"class":114,"line":1542},[112,23291,23292],{},"                \u003Ca href=\"\u002Fadmin\" class=\"btn btn-secondary\">クリア\u003C\u002Fa>\n",[112,23294,23295],{"class":114,"line":2402},[112,23296,23297],{},"            \u003C\u002Fform>\n",[112,23299,23300],{"class":114,"line":2407},[112,23301,23302],{},"        \u003C\u002Fdiv>\n",[112,23304,23305],{"class":114,"line":2413},[112,23306,23307],{},"    \u003C\u002Fdiv>\n",[112,23309,23310],{"class":114,"line":2446},[112,23311,147],{"emptyLinePlaceholder":146},[112,23313,23314],{"class":114,"line":2451},[112,23315,23316],{},"    \u003Cdiv class=\"mt-3\">\n",[112,23318,23319],{"class":114,"line":2464},[112,23320,23321],{},"        \u003Cp>検索結果: {{ $shops->total() }}件\u003C\u002Fp>\n",[112,23323,23324],{"class":114,"line":2481},[112,23325,23326],{},"        \u003Ctable class=\"table\">\n",[112,23328,23329],{"class":114,"line":2486},[112,23330,23331],{},"            \u003Cthead>\n",[112,23333,23334],{"class":114,"line":3229},[112,23335,23336],{},"                \u003Ctr>\n",[112,23338,23339],{"class":114,"line":3235},[112,23340,23341],{},"                    \u003Cth>ID\u003C\u002Fth>\n",[112,23343,23344],{"class":114,"line":3657},[112,23345,23346],{},"                    \u003Cth>名前\u003C\u002Fth>\n",[112,23348,23349],{"class":114,"line":3662},[112,23350,23351],{},"                    \u003Cth>年齢\u003C\u002Fth>\n",[112,23353,23354],{"class":114,"line":3667},[112,23355,23356],{},"                    \u003Cth>性別\u003C\u002Fth>\n",[112,23358,23359],{"class":114,"line":3680},[112,23360,23361],{},"                \u003C\u002Ftr>\n",[112,23363,23364],{"class":114,"line":3692},[112,23365,23366],{},"            \u003C\u002Fthead>\n",[112,23368,23369],{"class":114,"line":3716},[112,23370,23371],{},"            \u003Ctbody>\n",[112,23373,23374],{"class":114,"line":3737},[112,23375,23376],{},"                @foreach($shops as $shop)\n",[112,23378,23379],{"class":114,"line":3742},[112,23380,23381],{},"                    \u003Ctr>\n",[112,23383,23384],{"class":114,"line":3747},[112,23385,23386],{},"                        \u003Ctd>{{ $shop->id }}\u003C\u002Ftd>\n",[112,23388,23389],{"class":114,"line":3752},[112,23390,23391],{},"                        \u003Ctd>{{ $shop->name }}\u003C\u002Ftd>\n",[112,23393,23394],{"class":114,"line":3758},[112,23395,23396],{},"                        \u003Ctd>{{ $shop->age }}\u003C\u002Ftd>\n",[112,23398,23399],{"class":114,"line":3778},[112,23400,23401],{},"                        \u003Ctd>{{ $shop->gender_id === 1 ? '男性' : '女性' }}\u003C\u002Ftd>\n",[112,23403,23404],{"class":114,"line":3787},[112,23405,23406],{},"                    \u003C\u002Ftr>\n",[112,23408,23409],{"class":114,"line":3809},[112,23410,23411],{},"                @endforeach\n",[112,23413,23414],{"class":114,"line":3827},[112,23415,23416],{},"            \u003C\u002Ftbody>\n",[112,23418,23419],{"class":114,"line":3834},[112,23420,23421],{},"        \u003C\u002Ftable>\n",[112,23423,23424],{"class":114,"line":3848},[112,23425,23426],{},"        {{ $shops->links() }}\n",[112,23428,23429],{"class":114,"line":3858},[112,23430,23307],{},[112,23432,23433],{"class":114,"line":3863},[112,23434,23435],{},"\u003C\u002Fdiv>\n",[112,23437,23438],{"class":114,"line":3874},[112,23439,23440],{},"\u003C\u002Fbody>\n",[112,23442,23443],{"class":114,"line":3879},[112,23444,23445],{},"\u003C\u002Fhtml>\n",[11,23447,23448],{"id":23448},"セキュリティ上の注意点",[83,23450,23451],{"id":23451},"プリペアドステートメントの使用",[15,23453,23454,23457,23458,23461],{},[90,23455,23456],{},"whereRaw","を使う場合、必ずプレースホルダー（",[90,23459,23460],{},"?","）を使用してSQLインジェクション対策を行います。",[103,23463,23465],{"className":22391,"code":23464,"language":22372,"meta":108,"style":108},"\u002F\u002F ❌ 危険: SQLインジェクションのリスク\n$query->whereRaw(\"MATCH(free_word) AGAINST ('$freeWord' IN BOOLEAN MODE)\");\n\n\u002F\u002F ✅ 安全: プリペアドステートメント\n$query->whereRaw(\"MATCH(free_word) AGAINST (? IN BOOLEAN MODE)\", [$freeWord]);\n",[90,23466,23467,23471,23476,23480,23484],{"__ignoreMap":108},[112,23468,23469],{"class":114,"line":115},[112,23470,21275],{},[112,23472,23473],{"class":114,"line":122},[112,23474,23475],{},"$query->whereRaw(\"MATCH(free_word) AGAINST ('$freeWord' IN BOOLEAN MODE)\");\n",[112,23477,23478],{"class":114,"line":143},[112,23479,147],{"emptyLinePlaceholder":146},[112,23481,23482],{"class":114,"line":150},[112,23483,21303],{},[112,23485,23486],{"class":114,"line":170},[112,23487,23488],{},"$query->whereRaw(\"MATCH(free_word) AGAINST (? IN BOOLEAN MODE)\", [$freeWord]);\n",[83,23490,23491],{"id":23491},"入力値のバリデーション",[103,23493,23495],{"className":22391,"code":23494,"language":22372,"meta":108,"style":108},"$validated = $request->validate([\n    'free_word' => 'nullable|string|max:255',\n]);\n",[90,23496,23497,23502,23507],{"__ignoreMap":108},[112,23498,23499],{"class":114,"line":115},[112,23500,23501],{},"$validated = $request->validate([\n",[112,23503,23504],{"class":114,"line":122},[112,23505,23506],{},"    'free_word' => 'nullable|string|max:255',\n",[112,23508,23509],{"class":114,"line":143},[112,23510,3358],{},[11,23512,23514],{"id":23513},"like検索との比較","LIKE検索との比較",[83,23516,23518],{"id":23517},"like検索","LIKE検索",[103,23520,23522],{"className":22391,"code":23521,"language":22372,"meta":108,"style":108},"\u002F\u002F インデックスが使えない\n$query->where('name', 'LIKE', \"%{$keyword}%\");\n",[90,23523,23524,23529],{"__ignoreMap":108},[112,23525,23526],{"class":114,"line":115},[112,23527,23528],{},"\u002F\u002F インデックスが使えない\n",[112,23530,23531],{"class":114,"line":122},[112,23532,23533],{},"$query->where('name', 'LIKE', \"%{$keyword}%\");\n",[31,23535,23536,23541],{},[34,23537,23538,23540],{},[27,23539,21341],{},": シンプルな実装",[34,23542,23543,23546],{},[27,23544,23545],{},"デメリット",": データ量が増えると遅い、インデックスが使えない",[83,23548,23549],{"id":23549},"全文検索",[103,23551,23553],{"className":22391,"code":23552,"language":22372,"meta":108,"style":108},"\u002F\u002F 全文検索インデックスを使用\n$query->whereRaw(\"MATCH(free_word) AGAINST (? IN BOOLEAN MODE)\", [$keyword]);\n",[90,23554,23555,23560],{"__ignoreMap":108},[112,23556,23557],{"class":114,"line":115},[112,23558,23559],{},"\u002F\u002F 全文検索インデックスを使用\n",[112,23561,23562],{"class":114,"line":122},[112,23563,23564],{},"$query->whereRaw(\"MATCH(free_word) AGAINST (? IN BOOLEAN MODE)\", [$keyword]);\n",[31,23566,23567,23572],{},[34,23568,23569,23571],{},[27,23570,21341],{},": 高速、大量データでも性能が安定",[34,23573,23574,23576],{},[27,23575,23545],{},": セットアップが必要、ストレージ使用量が増える",[11,23578,919],{"id":919},[15,23580,23581],{},"MySQLの全文検索を使うことで",[31,23583,23584,23590,23596],{},[34,23585,23586,23589],{},[27,23587,23588],{},"高速な検索",": インデックスを活用した効率的な検索",[34,23591,23592,23595],{},[27,23593,23594],{},"日本語対応",": N-gramパーサーで日本語の部分一致検索が可能",[34,23597,23598,23601],{},[27,23599,23600],{},"柔軟な検索",": BOOLEANモードでAND\u002FOR\u002FNOT検索に対応",[15,23603,23604],{},"大量のテキストデータを検索する必要がある場合、全文検索の導入を検討する価値があります。",[83,23606,2930],{"id":2930},[31,23608,23609,23616,23622],{},[34,23610,23611],{},[759,23612,23615],{"href":23613,"rel":23614},"https:\u002F\u002Fdev.mysql.com\u002Fdoc\u002Frefman\u002F8.0\u002Fja\u002Ffulltext-search.html",[763],"MySQL 全文検索",[34,23617,23618],{},[759,23619,22342],{"href":23620,"rel":23621},"https:\u002F\u002Fdev.mysql.com\u002Fdoc\u002Frefman\u002F8.0\u002Fja\u002Ffulltext-search-ngram.html",[763],[34,23623,23624],{},[759,23625,23628],{"href":23626,"rel":23627},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fqueries",[763],"Laravel クエリビルダ",[944,23630,23631],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":108,"searchDepth":122,"depth":122,"links":23633},[23634,23635,23636,23646,23650,23654],{"id":22305,"depth":122,"text":22306},{"id":22335,"depth":122,"text":22336},{"id":22358,"depth":122,"text":22358,"children":23637},[23638,23639,23640,23641,23642,23643,23644,23645],{"id":22361,"depth":143,"text":22362},{"id":22387,"depth":143,"text":22388},{"id":22465,"depth":143,"text":22466},{"id":22702,"depth":143,"text":22702},{"id":22801,"depth":143,"text":22802},{"id":22927,"depth":143,"text":22928},{"id":23083,"depth":143,"text":23083},{"id":23166,"depth":143,"text":23167},{"id":23448,"depth":122,"text":23448,"children":23647},[23648,23649],{"id":23451,"depth":143,"text":23451},{"id":23491,"depth":143,"text":23491},{"id":23513,"depth":122,"text":23514,"children":23651},[23652,23653],{"id":23517,"depth":143,"text":23518},{"id":23549,"depth":143,"text":23549},{"id":919,"depth":122,"text":919,"children":23655},[23656],{"id":2930,"depth":143,"text":2930},"2022-01-02","MySQLのN-gram全文検索インデックスを使って、Laravelアプリケーションに高速な全文検索機能を実装する方法を解説します。",{"tags":23660},[23661,15436,23662],"laravel","fulltext-search","\u002Fblog\u002Flaravel-mysql-fulltext-search",{"title":22300,"description":23658},"blog\u002Flaravel-mysql-fulltext-search","YA6hFRcXoo23j2st071q-vuQtFTveg1AndRjg63Leu4",{"id":23668,"title":23669,"body":23670,"date":26488,"description":26489,"extension":962,"meta":26490,"navigation":146,"path":26494,"seo":26495,"stem":26496,"__hash__":26497},"blog\u002Fblog\u002Fnestjs-typeorm-crud-implementation.md","NestJS + TypeORM 0.3でCRUD APIを構築する",{"type":8,"value":23671,"toc":26448},[23672,23674,23677,23685,23688,23734,23737,23741,23748,23751,23765,23767,23771,23850,23854,23857,23944,23947,23979,23981,23997,24001,24004,24117,24124,24128,24131,24152,24155,24162,24166,24171,24258,24265,24269,24366,24379,24381,24547,24549,24685,24692,24695,24698,24724,24726,24749,24760,24763,24766,24782,24786,24863,24866,24974,24981,24985,24989,25101,25103,25712,25716,26226,26230,26233,26278,26281,26302,26305,26348,26351,26372,26375,26400,26402,26405,26410,26424,26428,26442,26445],[11,23673,13],{"id":13},[15,23675,23676],{},"NestJS V.9とTypeORM 0.3系を使った、最新のREST API構築方法を解説します。",[15,23678,23679,23680],{},"GitHubリポジトリ: ",[759,23681,23684],{"href":23682,"rel":23683},"https:\u002F\u002Fgithub.com\u002Fnaoki-haba\u002Fnestjs-demo-rest-api",[763],"nestjs-demo-rest-api",[11,23686,23687],{"id":23687},"環境",[103,23689,23691],{"className":771,"code":23690,"language":773,"meta":108,"style":108},"nest -v\n# 9.1.4\n\n# package.json\n\"@nestjs\u002Ftypeorm\": \"^9.0.1\"\n\"typeorm\": \"^0.3.10\"\n",[90,23692,23693,23700,23705,23709,23714,23724],{"__ignoreMap":108},[112,23694,23695,23697],{"class":114,"line":115},[112,23696,13154],{"class":163},[112,23698,23699],{"class":156}," -v\n",[112,23701,23702],{"class":114,"line":122},[112,23703,23704],{"class":118},"# 9.1.4\n",[112,23706,23707],{"class":114,"line":143},[112,23708,147],{"emptyLinePlaceholder":146},[112,23710,23711],{"class":114,"line":150},[112,23712,23713],{"class":118},"# package.json\n",[112,23715,23716,23719,23721],{"class":114,"line":170},[112,23717,23718],{"class":163},"\"@nestjs\u002Ftypeorm\"",[112,23720,2243],{"class":156},[112,23722,23723],{"class":136}," \"^9.0.1\"\n",[112,23725,23726,23729,23731],{"class":114,"line":182},[112,23727,23728],{"class":163},"\"typeorm\"",[112,23730,2243],{"class":156},[112,23732,23733],{"class":136}," \"^0.3.10\"\n",[15,23735,23736],{},"注意: Windows環境での動作確認は行っていません。",[11,23738,23740],{"id":23739},"nestjsとは","NestJSとは",[15,23742,23743,23747],{},[759,23744,12837],{"href":23745,"rel":23746},"https:\u002F\u002Fnestjs.com\u002F",[763],"は、効率的でスケーラブルなNode.jsサーバーサイドアプリケーションを構築するためのフレームワークです。",[15,23749,23750],{},"主な特徴：",[31,23752,23753,23756,23759,23762],{},[34,23754,23755],{},"TypeScriptを完全サポート",[34,23757,23758],{},"OOP（オブジェクト指向）、FP（関数型）、FRP（関数型リアクティブ）の要素を統合",[34,23760,23761],{},"Angular風のアーキテクチャ",[34,23763,23764],{},"依存性注入（DI）によるテスタビリティの向上",[11,23766,14707],{"id":14707},[83,23768,23770],{"id":23769},"nestjs-cliのインストール","NestJS CLIのインストール",[103,23772,23774],{"className":771,"code":23773,"language":773,"meta":108,"style":108},"# 作業ディレクトリを作成\nmkdir nestjs-sample\ncd nestjs-sample\n\n# NestJS CLIをグローバルにインストール\nnpm install -g @nestjs\u002Fcli\n\n# プロジェクトを作成（npmを選択）\nnest new sample\n\n# 依存関係をインストール\ncd sample\nnpm install\n",[90,23775,23776,23781,23788,23794,23798,23803,23813,23817,23821,23830,23834,23838,23844],{"__ignoreMap":108},[112,23777,23778],{"class":114,"line":115},[112,23779,23780],{"class":118},"# 作業ディレクトリを作成\n",[112,23782,23783,23785],{"class":114,"line":122},[112,23784,7268],{"class":163},[112,23786,23787],{"class":136}," nestjs-sample\n",[112,23789,23790,23792],{"class":114,"line":143},[112,23791,6891],{"class":156},[112,23793,23787],{"class":136},[112,23795,23796],{"class":114,"line":150},[112,23797,147],{"emptyLinePlaceholder":146},[112,23799,23800],{"class":114,"line":170},[112,23801,23802],{"class":118},"# NestJS CLIをグローバルにインストール\n",[112,23804,23805,23807,23809,23811],{"class":114,"line":182},[112,23806,6832],{"class":163},[112,23808,7883],{"class":136},[112,23810,14730],{"class":156},[112,23812,14733],{"class":136},[112,23814,23815],{"class":114,"line":193},[112,23816,147],{"emptyLinePlaceholder":146},[112,23818,23819],{"class":114,"line":205},[112,23820,14742],{"class":118},[112,23822,23823,23825,23827],{"class":114,"line":241},[112,23824,13154],{"class":163},[112,23826,232],{"class":136},[112,23828,23829],{"class":136}," sample\n",[112,23831,23832],{"class":114,"line":247},[112,23833,147],{"emptyLinePlaceholder":146},[112,23835,23836],{"class":114,"line":351},[112,23837,14775],{"class":118},[112,23839,23840,23842],{"class":114,"line":361},[112,23841,6891],{"class":156},[112,23843,23829],{"class":136},[112,23845,23846,23848],{"class":114,"line":367},[112,23847,6832],{"class":163},[112,23849,14782],{"class":136},[83,23851,23853],{"id":23852},"ポート番号の変更オプション","ポート番号の変更（オプション）",[15,23855,23856],{},"デフォルトの3000番ポートを変更する場合：",[103,23858,23860],{"className":105,"code":23859,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fmain.ts\nimport { NestFactory } from '@nestjs\u002Fcore'\nimport { AppModule } from '.\u002Fapp.module'\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule)\n  await app.listen(3001) \u002F\u002F ポート番号を変更\n}\nbootstrap()\n",[90,23861,23862,23866,23876,23886,23890,23900,23916,23934,23938],{"__ignoreMap":108},[112,23863,23864],{"class":114,"line":115},[112,23865,13992],{"class":118},[112,23867,23868,23870,23872,23874],{"class":114,"line":122},[112,23869,126],{"class":125},[112,23871,14847],{"class":129},[112,23873,133],{"class":125},[112,23875,13246],{"class":136},[112,23877,23878,23880,23882,23884],{"class":114,"line":143},[112,23879,126],{"class":125},[112,23881,14010],{"class":129},[112,23883,133],{"class":125},[112,23885,14015],{"class":136},[112,23887,23888],{"class":114,"line":150},[112,23889,147],{"emptyLinePlaceholder":146},[112,23891,23892,23894,23896,23898],{"class":114,"line":170},[112,23893,3305],{"class":125},[112,23895,1958],{"class":125},[112,23897,14051],{"class":163},[112,23899,3313],{"class":129},[112,23901,23902,23904,23906,23908,23910,23912,23914],{"class":114,"line":182},[112,23903,1974],{"class":125},[112,23905,14060],{"class":156},[112,23907,160],{"class":125},[112,23909,419],{"class":125},[112,23911,14067],{"class":129},[112,23913,14070],{"class":163},[112,23915,14892],{"class":129},[112,23917,23918,23920,23922,23924,23926,23929,23931],{"class":114,"line":193},[112,23919,5924],{"class":125},[112,23921,14109],{"class":129},[112,23923,14212],{"class":163},[112,23925,425],{"class":129},[112,23927,23928],{"class":156},"3001",[112,23930,226],{"class":129},[112,23932,23933],{"class":118},"\u002F\u002F ポート番号を変更\n",[112,23935,23936],{"class":114,"line":205},[112,23937,1452],{"class":129},[112,23939,23940,23942],{"class":114,"line":241},[112,23941,14228],{"class":163},[112,23943,630],{"class":129},[83,23945,23946],{"id":23946},"アプリケーションの起動",[103,23948,23950],{"className":771,"code":23949,"language":773,"meta":108,"style":108},"npm run start:dev\n\n# 以下のログが表示されればOK\n# [Nest] Starting Nest application...\n# [Nest] Nest application successfully started\n",[90,23951,23952,23960,23964,23969,23974],{"__ignoreMap":108},[112,23953,23954,23956,23958],{"class":114,"line":115},[112,23955,6832],{"class":163},[112,23957,6901],{"class":136},[112,23959,14800],{"class":136},[112,23961,23962],{"class":114,"line":122},[112,23963,147],{"emptyLinePlaceholder":146},[112,23965,23966],{"class":114,"line":143},[112,23967,23968],{"class":118},"# 以下のログが表示されればOK\n",[112,23970,23971],{"class":114,"line":150},[112,23972,23973],{"class":118},"# [Nest] Starting Nest application...\n",[112,23975,23976],{"class":114,"line":170},[112,23977,23978],{"class":118},"# [Nest] Nest application successfully started\n",[15,23980,14803],{},[103,23982,23984],{"className":771,"code":23983,"language":773,"meta":108,"style":108},"curl http:\u002F\u002Flocalhost:3001\n# \"Hello World!\" が返ればOK\n",[90,23985,23986,23993],{"__ignoreMap":108},[112,23987,23988,23990],{"class":114,"line":115},[112,23989,14330],{"class":163},[112,23991,23992],{"class":136}," http:\u002F\u002Flocalhost:3001\n",[112,23994,23995],{"class":114,"line":122},[112,23996,14820],{"class":118},[11,23998,24000],{"id":23999},"crudリソースの生成","CRUDリソースの生成",[15,24002,24003],{},"NestJS CLIを使って、CRUD操作の雛形を自動生成します。",[103,24005,24007],{"className":771,"code":24006,"language":773,"meta":108,"style":108},"# --no-specでテストファイルを生成しない\nnest generate resource users --no-spec\n\n# REST APIを選択\n? What transport layer do you use? REST API\n\n# CRUD entry pointsを有効化\n? Would you like to generate CRUD entry points? Yes\n\n# 以下のファイルが生成される\n# src\u002Fusers\u002Fusers.controller.ts\n# src\u002Fusers\u002Fusers.service.ts\n# src\u002Fusers\u002Fusers.module.ts\n# src\u002Fusers\u002Fdto\u002Fcreate-user.dto.ts\n# src\u002Fusers\u002Fdto\u002Fupdate-user.dto.ts\n# src\u002Fusers\u002Fentities\u002Fuser.entity.ts\n",[90,24008,24009,24014,24028,24032,24036,24058,24062,24066,24078,24082,24087,24092,24097,24102,24107,24112],{"__ignoreMap":108},[112,24010,24011],{"class":114,"line":115},[112,24012,24013],{"class":118},"# --no-specでテストファイルを生成しない\n",[112,24015,24016,24018,24020,24022,24025],{"class":114,"line":122},[112,24017,13154],{"class":163},[112,24019,15998],{"class":136},[112,24021,16001],{"class":136},[112,24023,24024],{"class":136}," users",[112,24026,24027],{"class":156}," --no-spec\n",[112,24029,24030],{"class":114,"line":143},[112,24031,147],{"emptyLinePlaceholder":146},[112,24033,24034],{"class":114,"line":150},[112,24035,16009],{"class":118},[112,24037,24038,24040,24043,24046,24049,24052,24055],{"class":114,"line":170},[112,24039,23460],{"class":125},[112,24041,24042],{"class":129}," What transport layer ",[112,24044,24045],{"class":125},"do",[112,24047,24048],{"class":163}," you",[112,24050,24051],{"class":136}," use?",[112,24053,24054],{"class":136}," REST",[112,24056,24057],{"class":136}," API\n",[112,24059,24060],{"class":114,"line":182},[112,24061,147],{"emptyLinePlaceholder":146},[112,24063,24064],{"class":114,"line":193},[112,24065,16014],{"class":118},[112,24067,24068,24070,24073,24075],{"class":114,"line":205},[112,24069,23460],{"class":125},[112,24071,24072],{"class":129}," Would you like to generate CRUD entry points",[112,24074,23460],{"class":125},[112,24076,24077],{"class":129}," Yes\n",[112,24079,24080],{"class":114,"line":241},[112,24081,147],{"emptyLinePlaceholder":146},[112,24083,24084],{"class":114,"line":247},[112,24085,24086],{"class":118},"# 以下のファイルが生成される\n",[112,24088,24089],{"class":114,"line":351},[112,24090,24091],{"class":118},"# src\u002Fusers\u002Fusers.controller.ts\n",[112,24093,24094],{"class":114,"line":361},[112,24095,24096],{"class":118},"# src\u002Fusers\u002Fusers.service.ts\n",[112,24098,24099],{"class":114,"line":367},[112,24100,24101],{"class":118},"# src\u002Fusers\u002Fusers.module.ts\n",[112,24103,24104],{"class":114,"line":373},[112,24105,24106],{"class":118},"# src\u002Fusers\u002Fdto\u002Fcreate-user.dto.ts\n",[112,24108,24109],{"class":114,"line":379},[112,24110,24111],{"class":118},"# src\u002Fusers\u002Fdto\u002Fupdate-user.dto.ts\n",[112,24113,24114],{"class":114,"line":1302},[112,24115,24116],{"class":118},"# src\u002Fusers\u002Fentities\u002Fuser.entity.ts\n",[15,24118,8011,24119],{},[759,24120,24123],{"href":24121,"rel":24122},"https:\u002F\u002Fdocs.nestjs.com\u002Frecipes\u002Fcrud-generator",[763],"CRUD Generator",[11,24125,24127],{"id":24126},"typeormのセットアップ","TypeORMのセットアップ",[83,24129,24130],{"id":24130},"パッケージのインストール",[103,24132,24134],{"className":771,"code":24133,"language":773,"meta":108,"style":108},"npm install --save @nestjs\u002Ftypeorm typeorm sqlite3\n",[90,24135,24136],{"__ignoreMap":108},[112,24137,24138,24140,24142,24144,24146,24149],{"class":114,"line":115},[112,24139,6832],{"class":163},[112,24141,7883],{"class":136},[112,24143,15470],{"class":156},[112,24145,15473],{"class":136},[112,24147,24148],{"class":136}," typeorm",[112,24150,24151],{"class":136}," sqlite3\n",[15,24153,24154],{},"今回は開発環境の簡便さのためSQLiteを使用しますが、本番環境ではMySQL、PostgreSQLなどの使用を推奨します。",[15,24156,14242,24157],{},[759,24158,24161],{"href":24159,"rel":24160},"https:\u002F\u002Fdocs.nestjs.com\u002Ftechniques\u002Fdatabase",[763],"NestJS Database",[83,24163,24165],{"id":24164},"typeorm設定ファイルの作成","TypeORM設定ファイルの作成",[15,24167,24168],{},[27,24169,24170],{},"⚠️ 警告：本番環境では環境変数から接続情報を取得してください。ハードコーディングは避けてください。",[103,24172,24174],{"className":105,"code":24173,"language":107,"meta":108,"style":108},"\u002F\u002F typeOrm.config.ts\nimport { DataSource } from 'typeorm'\n\nexport default new DataSource({\n  type: 'sqlite',\n  database: 'data\u002Fdev.sqlite',\n  entities: ['dist\u002F**\u002Fentities\u002F**\u002F*.entity.js'],\n  migrations: ['dist\u002F**\u002Fmigrations\u002F**\u002F*.js'],\n  logging: true,\n})\n",[90,24175,24176,24181,24192,24196,24209,24218,24228,24237,24246,24254],{"__ignoreMap":108},[112,24177,24178],{"class":114,"line":115},[112,24179,24180],{"class":118},"\u002F\u002F typeOrm.config.ts\n",[112,24182,24183,24185,24188,24190],{"class":114,"line":122},[112,24184,126],{"class":125},[112,24186,24187],{"class":129}," { DataSource } ",[112,24189,133],{"class":125},[112,24191,16078],{"class":136},[112,24193,24194],{"class":114,"line":143},[112,24195,147],{"emptyLinePlaceholder":146},[112,24197,24198,24200,24202,24204,24207],{"class":114,"line":150},[112,24199,288],{"class":125},[112,24201,291],{"class":125},[112,24203,232],{"class":125},[112,24205,24206],{"class":163}," DataSource",[112,24208,167],{"class":129},[112,24210,24211,24213,24216],{"class":114,"line":170},[112,24212,173],{"class":129},[112,24214,24215],{"class":136},"'sqlite'",[112,24217,179],{"class":129},[112,24219,24220,24223,24226],{"class":114,"line":182},[112,24221,24222],{"class":129},"  database: ",[112,24224,24225],{"class":136},"'data\u002Fdev.sqlite'",[112,24227,179],{"class":129},[112,24229,24230,24232,24235],{"class":114,"line":193},[112,24231,16574],{"class":129},[112,24233,24234],{"class":136},"'dist\u002F**\u002Fentities\u002F**\u002F*.entity.js'",[112,24236,746],{"class":129},[112,24238,24239,24241,24244],{"class":114,"line":205},[112,24240,16584],{"class":129},[112,24242,24243],{"class":136},"'dist\u002F**\u002Fmigrations\u002F**\u002F*.js'",[112,24245,746],{"class":129},[112,24247,24248,24250,24252],{"class":114,"line":241},[112,24249,16565],{"class":129},[112,24251,4345],{"class":156},[112,24253,179],{"class":129},[112,24255,24256],{"class":114,"line":247},[112,24257,8436],{"class":129},[15,24259,21440,24260],{},[759,24261,24264],{"href":24262,"rel":24263},"https:\u002F\u002Fwanago.io\u002F2022\u002F07\u002F25\u002Fapi-nestjs-database-migrations-typeorm\u002F",[763],"API with NestJS - Database migrations with TypeORM",[83,24266,24268],{"id":24267},"typeormコマンドの追加","TypeORMコマンドの追加",[103,24270,24272],{"className":2956,"code":24271,"language":2958,"meta":108,"style":108},"\u002F\u002F package.json\n{\n  \"scripts\": {\n    \"start:dev\": \"nest build && nest start --watch\",\n    \"typeorm\": \"ts-node .\u002Fnode_modules\u002Ftypeorm\u002Fcli\",\n    \"typeorm:run-migrations\": \"npm run typeorm migration:run -- -d .\u002FtypeOrm.config.ts\",\n    \"typeorm:generate-migration\": \"npm run typeorm -- -d .\u002FtypeOrm.config.ts migration:generate .\u002Fmigrations\u002F$npm_config_name\",\n    \"typeorm:create-migration\": \"npm run typeorm -- migration:create .\u002Fmigrations\u002F$npm_config_name\",\n    \"typeorm:revert-migration\": \"npm run typeorm -- -d .\u002FtypeOrm.config.ts migration:revert\"\n  }\n}\n",[90,24273,24274,24278,24282,24288,24300,24312,24324,24336,24348,24358,24362],{"__ignoreMap":108},[112,24275,24276],{"class":114,"line":115},[112,24277,18236],{"class":118},[112,24279,24280],{"class":114,"line":122},[112,24281,2965],{"class":129},[112,24283,24284,24286],{"class":114,"line":143},[112,24285,18245],{"class":156},[112,24287,852],{"class":129},[112,24289,24290,24293,24295,24298],{"class":114,"line":150},[112,24291,24292],{"class":156},"    \"start:dev\"",[112,24294,567],{"class":129},[112,24296,24297],{"class":136},"\"nest build && nest start --watch\"",[112,24299,179],{"class":129},[112,24301,24302,24305,24307,24310],{"class":114,"line":170},[112,24303,24304],{"class":156},"    \"typeorm\"",[112,24306,567],{"class":129},[112,24308,24309],{"class":136},"\"ts-node .\u002Fnode_modules\u002Ftypeorm\u002Fcli\"",[112,24311,179],{"class":129},[112,24313,24314,24317,24319,24322],{"class":114,"line":182},[112,24315,24316],{"class":156},"    \"typeorm:run-migrations\"",[112,24318,567],{"class":129},[112,24320,24321],{"class":136},"\"npm run typeorm migration:run -- -d .\u002FtypeOrm.config.ts\"",[112,24323,179],{"class":129},[112,24325,24326,24329,24331,24334],{"class":114,"line":193},[112,24327,24328],{"class":156},"    \"typeorm:generate-migration\"",[112,24330,567],{"class":129},[112,24332,24333],{"class":136},"\"npm run typeorm -- -d .\u002FtypeOrm.config.ts migration:generate .\u002Fmigrations\u002F$npm_config_name\"",[112,24335,179],{"class":129},[112,24337,24338,24341,24343,24346],{"class":114,"line":205},[112,24339,24340],{"class":156},"    \"typeorm:create-migration\"",[112,24342,567],{"class":129},[112,24344,24345],{"class":136},"\"npm run typeorm -- migration:create .\u002Fmigrations\u002F$npm_config_name\"",[112,24347,179],{"class":129},[112,24349,24350,24353,24355],{"class":114,"line":241},[112,24351,24352],{"class":156},"    \"typeorm:revert-migration\"",[112,24354,567],{"class":129},[112,24356,24357],{"class":136},"\"npm run typeorm -- -d .\u002FtypeOrm.config.ts migration:revert\"\n",[112,24359,24360],{"class":114,"line":247},[112,24361,3232],{"class":129},[112,24363,24364],{"class":114,"line":351},[112,24365,1452],{"class":129},[15,24367,24368,567,24371,24374,24375,24378],{},[27,24369,24370],{},"Windows環境の場合",[90,24372,24373],{},"$npm_config_name","を",[90,24376,24377],{},"%npm_config_name%","に変更してください。",[83,24380,15670],{"id":15669},[103,24382,24384],{"className":105,"code":24383,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fapp.module.ts\nimport { Module } from '@nestjs\u002Fcommon'\nimport { TypeOrmModule } from '@nestjs\u002Ftypeorm'\nimport { AppController } from '.\u002Fapp.controller'\nimport { AppService } from '.\u002Fapp.service'\nimport { UsersModule } from '.\u002Fusers\u002Fusers.module'\n\n@Module({\n  imports: [\n    UsersModule,\n    TypeOrmModule.forRoot({\n      type: 'sqlite',\n      database: 'data\u002Fdev.sqlite',\n      logging: true,\n      entities: ['dist\u002F**\u002Fentities\u002F**\u002F*.entity.js'],\n      migrations: ['dist\u002F**\u002Fmigrations\u002F**\u002F*.js'],\n    }),\n  ],\n  controllers: [AppController],\n  providers: [AppService],\n})\nexport class AppModule {}\n",[90,24385,24386,24391,24401,24411,24422,24432,24444,24448,24456,24460,24465,24473,24481,24490,24499,24508,24517,24521,24525,24529,24533,24537],{"__ignoreMap":108},[112,24387,24388],{"class":114,"line":115},[112,24389,24390],{"class":118},"\u002F\u002F src\u002Fapp.module.ts\n",[112,24392,24393,24395,24397,24399],{"class":114,"line":122},[112,24394,126],{"class":125},[112,24396,9863],{"class":129},[112,24398,133],{"class":125},[112,24400,9868],{"class":136},[112,24402,24403,24405,24407,24409],{"class":114,"line":143},[112,24404,126],{"class":125},[112,24406,15707],{"class":129},[112,24408,133],{"class":125},[112,24410,15712],{"class":136},[112,24412,24413,24415,24417,24419],{"class":114,"line":150},[112,24414,126],{"class":125},[112,24416,14947],{"class":129},[112,24418,133],{"class":125},[112,24420,24421],{"class":136}," '.\u002Fapp.controller'\n",[112,24423,24424,24426,24428,24430],{"class":114,"line":170},[112,24425,126],{"class":125},[112,24427,13819],{"class":129},[112,24429,133],{"class":125},[112,24431,13824],{"class":136},[112,24433,24434,24436,24439,24441],{"class":114,"line":182},[112,24435,126],{"class":125},[112,24437,24438],{"class":129}," { UsersModule } ",[112,24440,133],{"class":125},[112,24442,24443],{"class":136}," '.\u002Fusers\u002Fusers.module'\n",[112,24445,24446],{"class":114,"line":193},[112,24447,147],{"emptyLinePlaceholder":146},[112,24449,24450,24452,24454],{"class":114,"line":205},[112,24451,9901],{"class":129},[112,24453,9851],{"class":163},[112,24455,167],{"class":129},[112,24457,24458],{"class":114,"line":241},[112,24459,15753],{"class":129},[112,24461,24462],{"class":114,"line":247},[112,24463,24464],{"class":129},"    UsersModule,\n",[112,24466,24467,24469,24471],{"class":114,"line":351},[112,24468,15786],{"class":129},[112,24470,15761],{"class":163},[112,24472,167],{"class":129},[112,24474,24475,24477,24479],{"class":114,"line":361},[112,24476,315],{"class":129},[112,24478,24215],{"class":136},[112,24480,179],{"class":129},[112,24482,24483,24486,24488],{"class":114,"line":367},[112,24484,24485],{"class":129},"      database: ",[112,24487,24225],{"class":136},[112,24489,179],{"class":129},[112,24491,24492,24495,24497],{"class":114,"line":373},[112,24493,24494],{"class":129},"      logging: ",[112,24496,4345],{"class":156},[112,24498,179],{"class":129},[112,24500,24501,24504,24506],{"class":114,"line":379},[112,24502,24503],{"class":129},"      entities: [",[112,24505,24234],{"class":136},[112,24507,746],{"class":129},[112,24509,24510,24513,24515],{"class":114,"line":1302},[112,24511,24512],{"class":129},"      migrations: [",[112,24514,24243],{"class":136},[112,24516,746],{"class":129},[112,24518,24519],{"class":114,"line":1502},[112,24520,370],{"class":129},[112,24522,24523],{"class":114,"line":1507},[112,24524,1887],{"class":129},[112,24526,24527],{"class":114,"line":1512},[112,24528,14985],{"class":129},[112,24530,24531],{"class":114,"line":1518},[112,24532,14990],{"class":129},[112,24534,24535],{"class":114,"line":1524},[112,24536,8436],{"class":129},[112,24538,24539,24541,24543,24545],{"class":114,"line":1530},[112,24540,288],{"class":125},[112,24542,9931],{"class":125},[112,24544,15003],{"class":163},[112,24546,9937],{"class":129},[11,24548,16018],{"id":16017},[103,24550,24552],{"className":105,"code":24551,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fusers\u002Fentities\u002Fuser.entity.ts\nimport { Column, PrimaryGeneratedColumn, Entity } from 'typeorm'\n\n@Entity('users')\nexport class User {\n  @PrimaryGeneratedColumn({ comment: 'アカウントID' })\n  readonly id: number\n\n  @Column('varchar', { comment: 'アカウント名' })\n  name: string\n\n  constructor(name: string) {\n    this.name = name\n  }\n}\n",[90,24553,24554,24559,24570,24574,24586,24596,24609,24619,24623,24641,24649,24653,24667,24677,24681],{"__ignoreMap":108},[112,24555,24556],{"class":114,"line":115},[112,24557,24558],{"class":118},"\u002F\u002F src\u002Fusers\u002Fentities\u002Fuser.entity.ts\n",[112,24560,24561,24563,24566,24568],{"class":114,"line":122},[112,24562,126],{"class":125},[112,24564,24565],{"class":129}," { Column, PrimaryGeneratedColumn, Entity } ",[112,24567,133],{"class":125},[112,24569,16078],{"class":136},[112,24571,24572],{"class":114,"line":143},[112,24573,147],{"emptyLinePlaceholder":146},[112,24575,24576,24578,24580,24582,24584],{"class":114,"line":150},[112,24577,9901],{"class":129},[112,24579,16089],{"class":163},[112,24581,425],{"class":129},[112,24583,11037],{"class":136},[112,24585,431],{"class":129},[112,24587,24588,24590,24592,24594],{"class":114,"line":170},[112,24589,288],{"class":125},[112,24591,9931],{"class":125},[112,24593,16104],{"class":163},[112,24595,1294],{"class":129},[112,24597,24598,24600,24602,24604,24607],{"class":114,"line":182},[112,24599,11079],{"class":129},[112,24601,16113],{"class":163},[112,24603,16295],{"class":129},[112,24605,24606],{"class":136},"'アカウントID'",[112,24608,11585],{"class":129},[112,24610,24611,24613,24615,24617],{"class":114,"line":193},[112,24612,16163],{"class":125},[112,24614,16166],{"class":222},[112,24616,2243],{"class":125},[112,24618,16171],{"class":156},[112,24620,24621],{"class":114,"line":205},[112,24622,147],{"emptyLinePlaceholder":146},[112,24624,24625,24627,24629,24631,24633,24636,24639],{"class":114,"line":241},[112,24626,11079],{"class":129},[112,24628,16182],{"class":163},[112,24630,425],{"class":129},[112,24632,16188],{"class":136},[112,24634,24635],{"class":129},", { comment: ",[112,24637,24638],{"class":136},"'アカウント名'",[112,24640,11585],{"class":129},[112,24642,24643,24645,24647],{"class":114,"line":247},[112,24644,10020],{"class":222},[112,24646,2243],{"class":125},[112,24648,10006],{"class":156},[112,24650,24651],{"class":114,"line":351},[112,24652,147],{"emptyLinePlaceholder":146},[112,24654,24655,24657,24659,24661,24663,24665],{"class":114,"line":361},[112,24656,10128],{"class":125},[112,24658,425],{"class":129},[112,24660,16383],{"class":222},[112,24662,2243],{"class":125},[112,24664,10478],{"class":156},[112,24666,1969],{"class":129},[112,24668,24669,24671,24673,24675],{"class":114,"line":367},[112,24670,10148],{"class":156},[112,24672,16413],{"class":129},[112,24674,576],{"class":125},[112,24676,16418],{"class":129},[112,24678,24679],{"class":114,"line":373},[112,24680,3232],{"class":129},[112,24682,24683],{"class":114,"line":379},[112,24684,1452],{"class":129},[15,24686,8011,24687],{},[759,24688,24691],{"href":24689,"rel":24690},"https:\u002F\u002Fdocs.nestjs.com\u002Ftechniques\u002Fdatabase#repository-pattern",[763],"NestJS Database - Entities",[11,24693,24694],{"id":24694},"マイグレーション",[83,24696,24697],{"id":24697},"マイグレーションファイルの生成",[103,24699,24701],{"className":771,"code":24700,"language":773,"meta":108,"style":108},"npm run typeorm:generate-migration --name=CreateUser\n\n# Migration .\u002Fmigrations\u002F1665664827418-CreateUser.ts has been generated successfully.\n",[90,24702,24703,24715,24719],{"__ignoreMap":108},[112,24704,24705,24707,24709,24712],{"class":114,"line":115},[112,24706,6832],{"class":163},[112,24708,6901],{"class":136},[112,24710,24711],{"class":136}," typeorm:generate-migration",[112,24713,24714],{"class":156}," --name=CreateUser\n",[112,24716,24717],{"class":114,"line":122},[112,24718,147],{"emptyLinePlaceholder":146},[112,24720,24721],{"class":114,"line":143},[112,24722,24723],{"class":118},"# Migration .\u002Fmigrations\u002F1665664827418-CreateUser.ts has been generated successfully.\n",[83,24725,16625],{"id":16625},[103,24727,24729],{"className":771,"code":24728,"language":773,"meta":108,"style":108},"npm run typeorm:run-migrations\n\n# Migration CreateUser1665664827418 has been executed successfully.\n",[90,24730,24731,24740,24744],{"__ignoreMap":108},[112,24732,24733,24735,24737],{"class":114,"line":115},[112,24734,6832],{"class":163},[112,24736,6901],{"class":136},[112,24738,24739],{"class":136}," typeorm:run-migrations\n",[112,24741,24742],{"class":114,"line":122},[112,24743,147],{"emptyLinePlaceholder":146},[112,24745,24746],{"class":114,"line":143},[112,24747,24748],{"class":118},"# Migration CreateUser1665664827418 has been executed successfully.\n",[15,24750,24751,24752,24755,24756,24759],{},"マイグレーション実行後、",[90,24753,24754],{},"data\u002Fdev.sqlite","ファイルが作成され、",[90,24757,24758],{},"users","テーブルが生成されます。",[11,24761,24762],{"id":24762},"バリデーションの実装",[83,24764,24130],{"id":24765},"パッケージのインストール-1",[103,24767,24768],{"className":771,"code":16700,"language":773,"meta":108,"style":108},[90,24769,24770],{"__ignoreMap":108},[112,24771,24772,24774,24776,24778,24780],{"class":114,"line":115},[112,24773,6832],{"class":163},[112,24775,7883],{"class":136},[112,24777,15470],{"class":156},[112,24779,11479],{"class":136},[112,24781,11482],{"class":136},[83,24783,24785],{"id":24784},"dtoへのバリデーション追加","DTOへのバリデーション追加",[103,24787,24789],{"className":105,"code":24788,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fusers\u002Fdto\u002Fcreate-user.dto.ts\nimport { IsNotEmpty, MaxLength } from 'class-validator'\n\nexport class CreateUserDto {\n  @IsNotEmpty({ message: 'アカウント名は必須です' })\n  @MaxLength(255, { message: 'アカウント名は255文字以内で入力してください' })\n  name: string\n}\n",[90,24790,24791,24796,24807,24811,24821,24834,24851,24859],{"__ignoreMap":108},[112,24792,24793],{"class":114,"line":115},[112,24794,24795],{"class":118},"\u002F\u002F src\u002Fusers\u002Fdto\u002Fcreate-user.dto.ts\n",[112,24797,24798,24800,24803,24805],{"class":114,"line":122},[112,24799,126],{"class":125},[112,24801,24802],{"class":129}," { IsNotEmpty, MaxLength } ",[112,24804,133],{"class":125},[112,24806,11366],{"class":136},[112,24808,24809],{"class":114,"line":143},[112,24810,147],{"emptyLinePlaceholder":146},[112,24812,24813,24815,24817,24819],{"class":114,"line":150},[112,24814,288],{"class":125},[112,24816,9931],{"class":125},[112,24818,10957],{"class":163},[112,24820,1294],{"class":129},[112,24822,24823,24825,24827,24829,24832],{"class":114,"line":170},[112,24824,11079],{"class":129},[112,24826,16756],{"class":163},[112,24828,16759],{"class":129},[112,24830,24831],{"class":136},"'アカウント名は必須です'",[112,24833,11585],{"class":129},[112,24835,24836,24838,24840,24842,24844,24846,24849],{"class":114,"line":182},[112,24837,11079],{"class":129},[112,24839,16771],{"class":163},[112,24841,425],{"class":129},[112,24843,16194],{"class":156},[112,24845,16778],{"class":129},[112,24847,24848],{"class":136},"'アカウント名は255文字以内で入力してください'",[112,24850,11585],{"class":129},[112,24852,24853,24855,24857],{"class":114,"line":193},[112,24854,10020],{"class":222},[112,24856,2243],{"class":125},[112,24858,10006],{"class":156},[112,24860,24861],{"class":114,"line":205},[112,24862,1452],{"class":129},[83,24864,24865],{"id":24865},"グローバルバリデーションの有効化",[103,24867,24869],{"className":105,"code":24868,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fmain.ts\nimport { NestFactory } from '@nestjs\u002Fcore'\nimport { AppModule } from '.\u002Fapp.module'\nimport { ValidationPipe } from '@nestjs\u002Fcommon'\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule)\n  app.useGlobalPipes(new ValidationPipe())\n  await app.listen(3001)\n}\nbootstrap()\n",[90,24870,24871,24875,24885,24895,24906,24910,24920,24936,24950,24964,24968],{"__ignoreMap":108},[112,24872,24873],{"class":114,"line":115},[112,24874,13992],{"class":118},[112,24876,24877,24879,24881,24883],{"class":114,"line":122},[112,24878,126],{"class":125},[112,24880,14847],{"class":129},[112,24882,133],{"class":125},[112,24884,13246],{"class":136},[112,24886,24887,24889,24891,24893],{"class":114,"line":143},[112,24888,126],{"class":125},[112,24890,14010],{"class":129},[112,24892,133],{"class":125},[112,24894,14015],{"class":136},[112,24896,24897,24899,24902,24904],{"class":114,"line":150},[112,24898,126],{"class":125},[112,24900,24901],{"class":129}," { ValidationPipe } ",[112,24903,133],{"class":125},[112,24905,9868],{"class":136},[112,24907,24908],{"class":114,"line":170},[112,24909,147],{"emptyLinePlaceholder":146},[112,24911,24912,24914,24916,24918],{"class":114,"line":182},[112,24913,3305],{"class":125},[112,24915,1958],{"class":125},[112,24917,14051],{"class":163},[112,24919,3313],{"class":129},[112,24921,24922,24924,24926,24928,24930,24932,24934],{"class":114,"line":193},[112,24923,1974],{"class":125},[112,24925,14060],{"class":156},[112,24927,160],{"class":125},[112,24929,419],{"class":125},[112,24931,14067],{"class":129},[112,24933,14070],{"class":163},[112,24935,14892],{"class":129},[112,24937,24938,24940,24942,24944,24946,24948],{"class":114,"line":205},[112,24939,14082],{"class":129},[112,24941,17811],{"class":163},[112,24943,425],{"class":129},[112,24945,14155],{"class":125},[112,24947,17818],{"class":163},[112,24949,17821],{"class":129},[112,24951,24952,24954,24956,24958,24960,24962],{"class":114,"line":241},[112,24953,5924],{"class":125},[112,24955,14109],{"class":129},[112,24957,14212],{"class":163},[112,24959,425],{"class":129},[112,24961,23928],{"class":156},[112,24963,431],{"class":129},[112,24965,24966],{"class":114,"line":247},[112,24967,1452],{"class":129},[112,24969,24970,24972],{"class":114,"line":351},[112,24971,14228],{"class":163},[112,24973,630],{"class":129},[15,24975,8011,24976],{},[759,24977,24980],{"href":24978,"rel":24979},"https:\u002F\u002Fdocs.nestjs.com\u002Ftechniques\u002Fvalidation",[763],"NestJS Validation",[11,24982,24984],{"id":24983},"crud機能の実装","CRUD機能の実装",[83,24986,24988],{"id":24987},"usersmoduleへのtypeorm統合","UsersModuleへのTypeORM統合",[103,24990,24992],{"className":105,"code":24991,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fusers\u002Fusers.module.ts\nimport { Module } from '@nestjs\u002Fcommon'\nimport { TypeOrmModule } from '@nestjs\u002Ftypeorm'\nimport { UsersService } from '.\u002Fusers.service'\nimport { UsersController } from '.\u002Fusers.controller'\nimport { User } from '.\u002Fentities\u002Fuser.entity'\n\n@Module({\n  imports: [TypeOrmModule.forFeature([User])],\n  controllers: [UsersController],\n  providers: [UsersService],\n})\nexport class UsersModule {}\n",[90,24993,24994,24999,25009,25019,25031,25043,25054,25058,25066,25076,25081,25086,25090],{"__ignoreMap":108},[112,24995,24996],{"class":114,"line":115},[112,24997,24998],{"class":118},"\u002F\u002F src\u002Fusers\u002Fusers.module.ts\n",[112,25000,25001,25003,25005,25007],{"class":114,"line":122},[112,25002,126],{"class":125},[112,25004,9863],{"class":129},[112,25006,133],{"class":125},[112,25008,9868],{"class":136},[112,25010,25011,25013,25015,25017],{"class":114,"line":143},[112,25012,126],{"class":125},[112,25014,15707],{"class":129},[112,25016,133],{"class":125},[112,25018,15712],{"class":136},[112,25020,25021,25023,25026,25028],{"class":114,"line":150},[112,25022,126],{"class":125},[112,25024,25025],{"class":129}," { UsersService } ",[112,25027,133],{"class":125},[112,25029,25030],{"class":136}," '.\u002Fusers.service'\n",[112,25032,25033,25035,25038,25040],{"class":114,"line":170},[112,25034,126],{"class":125},[112,25036,25037],{"class":129}," { UsersController } ",[112,25039,133],{"class":125},[112,25041,25042],{"class":136}," '.\u002Fusers.controller'\n",[112,25044,25045,25047,25049,25051],{"class":114,"line":182},[112,25046,126],{"class":125},[112,25048,17025],{"class":129},[112,25050,133],{"class":125},[112,25052,25053],{"class":136}," '.\u002Fentities\u002Fuser.entity'\n",[112,25055,25056],{"class":114,"line":193},[112,25057,147],{"emptyLinePlaceholder":146},[112,25059,25060,25062,25064],{"class":114,"line":205},[112,25061,9901],{"class":129},[112,25063,9851],{"class":163},[112,25065,167],{"class":129},[112,25067,25068,25071,25073],{"class":114,"line":241},[112,25069,25070],{"class":129},"  imports: [TypeOrmModule.",[112,25072,17773],{"class":163},[112,25074,25075],{"class":129},"([User])],\n",[112,25077,25078],{"class":114,"line":247},[112,25079,25080],{"class":129},"  controllers: [UsersController],\n",[112,25082,25083],{"class":114,"line":351},[112,25084,25085],{"class":129},"  providers: [UsersService],\n",[112,25087,25088],{"class":114,"line":361},[112,25089,8436],{"class":129},[112,25091,25092,25094,25096,25099],{"class":114,"line":367},[112,25093,288],{"class":125},[112,25095,9931],{"class":125},[112,25097,25098],{"class":163}," UsersModule",[112,25100,9937],{"class":129},[83,25102,16950],{"id":16949},[103,25104,25106],{"className":105,"code":25105,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fusers\u002Fusers.service.ts\nimport { Injectable, InternalServerErrorException } from '@nestjs\u002Fcommon'\nimport { InjectRepository } from '@nestjs\u002Ftypeorm'\nimport { Repository } from 'typeorm'\nimport { User } from '.\u002Fentities\u002Fuser.entity'\nimport { CreateUserDto } from '.\u002Fdto\u002Fcreate-user.dto'\nimport { UpdateUserDto } from '.\u002Fdto\u002Fupdate-user.dto'\n\n@Injectable()\nexport class UsersService {\n  constructor(\n    @InjectRepository(User)\n    private userRepository: Repository\u003CUser>,\n  ) {}\n\n  async create(createUserDto: CreateUserDto): Promise\u003C{ message: string }> {\n    await this.userRepository\n      .save({ name: createUserDto.name })\n      .catch((e) => {\n        throw new InternalServerErrorException(\n          `[${e.message}]アカウントの登録に失敗しました。`,\n        )\n      })\n\n    return { message: 'アカウントの登録に成功しました' }\n  }\n\n  async findAll(): Promise\u003CUser[]> {\n    return await this.userRepository.find()\n  }\n\n  async findOne(id: number): Promise\u003CUser> {\n    return await this.userRepository.findOneBy({ id })\n  }\n\n  async update(id: number, updateUserDto: UpdateUserDto): Promise\u003C{ message: string }> {\n    await this.userRepository\n      .update(id, { name: updateUserDto.name })\n      .catch((e) => {\n        throw new InternalServerErrorException(\n          `[${e.message}]アカウントID「${id}」の更新に失敗しました。`,\n        )\n      })\n\n    return { message: `アカウントID「${id}」の更新に成功しました。` }\n  }\n\n  async remove(id: number): Promise\u003C{ message: string }> {\n    await this.userRepository.delete(id).catch((e) => {\n      throw new InternalServerErrorException(\n        `[${e.message}]アカウントID「${id}」の削除に失敗しました。`,\n      )\n    })\n\n    return { message: `アカウントID「${id}」の削除に成功しました。` }\n  }\n}\n",[90,25107,25108,25113,25123,25133,25143,25153,25164,25176,25180,25188,25198,25204,25212,25229,25233,25237,25267,25276,25286,25302,25312,25328,25333,25337,25341,25352,25356,25360,25378,25393,25397,25401,25427,25443,25447,25451,25491,25499,25509,25525,25535,25555,25559,25563,25567,25583,25587,25591,25622,25647,25657,25677,25681,25685,25689,25704,25708],{"__ignoreMap":108},[112,25109,25110],{"class":114,"line":115},[112,25111,25112],{"class":118},"\u002F\u002F src\u002Fusers\u002Fusers.service.ts\n",[112,25114,25115,25117,25119,25121],{"class":114,"line":122},[112,25116,126],{"class":125},[112,25118,9953],{"class":129},[112,25120,133],{"class":125},[112,25122,9868],{"class":136},[112,25124,25125,25127,25129,25131],{"class":114,"line":143},[112,25126,126],{"class":125},[112,25128,17003],{"class":129},[112,25130,133],{"class":125},[112,25132,15712],{"class":136},[112,25134,25135,25137,25139,25141],{"class":114,"line":150},[112,25136,126],{"class":125},[112,25138,17014],{"class":129},[112,25140,133],{"class":125},[112,25142,16078],{"class":136},[112,25144,25145,25147,25149,25151],{"class":114,"line":170},[112,25146,126],{"class":125},[112,25148,17025],{"class":129},[112,25150,133],{"class":125},[112,25152,25053],{"class":136},[112,25154,25155,25157,25159,25161],{"class":114,"line":182},[112,25156,126],{"class":125},[112,25158,17037],{"class":129},[112,25160,133],{"class":125},[112,25162,25163],{"class":136}," '.\u002Fdto\u002Fcreate-user.dto'\n",[112,25165,25166,25168,25171,25173],{"class":114,"line":193},[112,25167,126],{"class":125},[112,25169,25170],{"class":129}," { UpdateUserDto } ",[112,25172,133],{"class":125},[112,25174,25175],{"class":136}," '.\u002Fdto\u002Fupdate-user.dto'\n",[112,25177,25178],{"class":114,"line":205},[112,25179,147],{"emptyLinePlaceholder":146},[112,25181,25182,25184,25186],{"class":114,"line":241},[112,25183,9901],{"class":129},[112,25185,10076],{"class":163},[112,25187,630],{"class":129},[112,25189,25190,25192,25194,25196],{"class":114,"line":247},[112,25191,288],{"class":125},[112,25193,9931],{"class":125},[112,25195,17081],{"class":163},[112,25197,1294],{"class":129},[112,25199,25200,25202],{"class":114,"line":351},[112,25201,10128],{"class":125},[112,25203,2304],{"class":129},[112,25205,25206,25208,25210],{"class":114,"line":361},[112,25207,11218],{"class":129},[112,25209,17096],{"class":163},[112,25211,17099],{"class":129},[112,25213,25214,25216,25219,25221,25223,25225,25227],{"class":114,"line":367},[112,25215,13386],{"class":125},[112,25217,25218],{"class":222}," userRepository",[112,25220,2243],{"class":125},[112,25222,17111],{"class":163},[112,25224,6944],{"class":129},[112,25226,10489],{"class":163},[112,25228,17118],{"class":129},[112,25230,25231],{"class":114,"line":373},[112,25232,17123],{"class":129},[112,25234,25235],{"class":114,"line":379},[112,25236,147],{"emptyLinePlaceholder":146},[112,25238,25239,25241,25243,25245,25247,25249,25251,25253,25255,25257,25259,25261,25263,25265],{"class":114,"line":1302},[112,25240,10270],{"class":125},[112,25242,6835],{"class":163},[112,25244,425],{"class":129},[112,25246,17138],{"class":222},[112,25248,2243],{"class":125},[112,25250,10957],{"class":163},[112,25252,10186],{"class":129},[112,25254,2243],{"class":125},[112,25256,10289],{"class":163},[112,25258,17151],{"class":129},[112,25260,10811],{"class":222},[112,25262,2243],{"class":125},[112,25264,10478],{"class":156},[112,25266,17160],{"class":129},[112,25268,25269,25271,25273],{"class":114,"line":1502},[112,25270,3385],{"class":125},[112,25272,10318],{"class":156},[112,25274,25275],{"class":129},".userRepository\n",[112,25277,25278,25281,25283],{"class":114,"line":1507},[112,25279,25280],{"class":129},"      .",[112,25282,17236],{"class":163},[112,25284,25285],{"class":129},"({ name: createUserDto.name })\n",[112,25287,25288,25290,25292,25294,25296,25298,25300],{"class":114,"line":1512},[112,25289,25280],{"class":129},[112,25291,3686],{"class":163},[112,25293,219],{"class":129},[112,25295,6950],{"class":222},[112,25297,226],{"class":129},[112,25299,229],{"class":125},[112,25301,1294],{"class":129},[112,25303,25304,25306,25308,25310],{"class":114,"line":1518},[112,25305,10385],{"class":125},[112,25307,232],{"class":125},[112,25309,10761],{"class":163},[112,25311,2304],{"class":129},[112,25313,25314,25317,25319,25321,25323,25326],{"class":114,"line":1524},[112,25315,25316],{"class":136},"          `[${",[112,25318,6950],{"class":129},[112,25320,2124],{"class":136},[112,25322,10811],{"class":129},[112,25324,25325],{"class":136},"}]アカウントの登録に失敗しました。`",[112,25327,179],{"class":129},[112,25329,25330],{"class":114,"line":1530},[112,25331,25332],{"class":129},"        )\n",[112,25334,25335],{"class":114,"line":1536},[112,25336,8186],{"class":129},[112,25338,25339],{"class":114,"line":1542},[112,25340,147],{"emptyLinePlaceholder":146},[112,25342,25343,25345,25347,25350],{"class":114,"line":2402},[112,25344,3973],{"class":125},[112,25346,11332],{"class":129},[112,25348,25349],{"class":136},"'アカウントの登録に成功しました'",[112,25351,2395],{"class":129},[112,25353,25354],{"class":114,"line":2407},[112,25355,3232],{"class":129},[112,25357,25358],{"class":114,"line":2413},[112,25359,147],{"emptyLinePlaceholder":146},[112,25361,25362,25364,25366,25368,25370,25372,25374,25376],{"class":114,"line":2446},[112,25363,10270],{"class":125},[112,25365,17302],{"class":163},[112,25367,13899],{"class":129},[112,25369,2243],{"class":125},[112,25371,10289],{"class":163},[112,25373,6944],{"class":129},[112,25375,10489],{"class":163},[112,25377,17315],{"class":129},[112,25379,25380,25382,25384,25386,25389,25391],{"class":114,"line":2451},[112,25381,3973],{"class":125},[112,25383,419],{"class":125},[112,25385,10318],{"class":156},[112,25387,25388],{"class":129},".userRepository.",[112,25390,17326],{"class":163},[112,25392,630],{"class":129},[112,25394,25395],{"class":114,"line":2464},[112,25396,3232],{"class":129},[112,25398,25399],{"class":114,"line":2481},[112,25400,147],{"emptyLinePlaceholder":146},[112,25402,25403,25405,25407,25409,25411,25413,25415,25417,25419,25421,25423,25425],{"class":114,"line":2486},[112,25404,10270],{"class":125},[112,25406,17343],{"class":163},[112,25408,425],{"class":129},[112,25410,17348],{"class":222},[112,25412,2243],{"class":125},[112,25414,17353],{"class":156},[112,25416,10186],{"class":129},[112,25418,2243],{"class":125},[112,25420,10289],{"class":163},[112,25422,6944],{"class":129},[112,25424,10489],{"class":163},[112,25426,10297],{"class":129},[112,25428,25429,25431,25433,25435,25437,25440],{"class":114,"line":3229},[112,25430,3973],{"class":125},[112,25432,419],{"class":125},[112,25434,10318],{"class":156},[112,25436,25388],{"class":129},[112,25438,25439],{"class":163},"findOneBy",[112,25441,25442],{"class":129},"({ id })\n",[112,25444,25445],{"class":114,"line":3235},[112,25446,3232],{"class":129},[112,25448,25449],{"class":114,"line":3657},[112,25450,147],{"emptyLinePlaceholder":146},[112,25452,25453,25455,25458,25460,25462,25464,25466,25468,25471,25473,25475,25477,25479,25481,25483,25485,25487,25489],{"class":114,"line":3662},[112,25454,10270],{"class":125},[112,25456,25457],{"class":163}," update",[112,25459,425],{"class":129},[112,25461,17348],{"class":222},[112,25463,2243],{"class":125},[112,25465,17353],{"class":156},[112,25467,447],{"class":129},[112,25469,25470],{"class":222},"updateUserDto",[112,25472,2243],{"class":125},[112,25474,11000],{"class":163},[112,25476,10186],{"class":129},[112,25478,2243],{"class":125},[112,25480,10289],{"class":163},[112,25482,17151],{"class":129},[112,25484,10811],{"class":222},[112,25486,2243],{"class":125},[112,25488,10478],{"class":156},[112,25490,17160],{"class":129},[112,25492,25493,25495,25497],{"class":114,"line":3667},[112,25494,3385],{"class":125},[112,25496,10318],{"class":156},[112,25498,25275],{"class":129},[112,25500,25501,25503,25506],{"class":114,"line":3680},[112,25502,25280],{"class":129},[112,25504,25505],{"class":163},"update",[112,25507,25508],{"class":129},"(id, { name: updateUserDto.name })\n",[112,25510,25511,25513,25515,25517,25519,25521,25523],{"class":114,"line":3692},[112,25512,25280],{"class":129},[112,25514,3686],{"class":163},[112,25516,219],{"class":129},[112,25518,6950],{"class":222},[112,25520,226],{"class":129},[112,25522,229],{"class":125},[112,25524,1294],{"class":129},[112,25526,25527,25529,25531,25533],{"class":114,"line":3716},[112,25528,10385],{"class":125},[112,25530,232],{"class":125},[112,25532,10761],{"class":163},[112,25534,2304],{"class":129},[112,25536,25537,25539,25541,25543,25545,25548,25550,25553],{"class":114,"line":3737},[112,25538,25316],{"class":136},[112,25540,6950],{"class":129},[112,25542,2124],{"class":136},[112,25544,10811],{"class":129},[112,25546,25547],{"class":136},"}]アカウントID「${",[112,25549,17348],{"class":129},[112,25551,25552],{"class":136},"}」の更新に失敗しました。`",[112,25554,179],{"class":129},[112,25556,25557],{"class":114,"line":3742},[112,25558,25332],{"class":129},[112,25560,25561],{"class":114,"line":3747},[112,25562,8186],{"class":129},[112,25564,25565],{"class":114,"line":3752},[112,25566,147],{"emptyLinePlaceholder":146},[112,25568,25569,25571,25573,25576,25578,25581],{"class":114,"line":3758},[112,25570,3973],{"class":125},[112,25572,11332],{"class":129},[112,25574,25575],{"class":136},"`アカウントID「${",[112,25577,17348],{"class":129},[112,25579,25580],{"class":136},"}」の更新に成功しました。`",[112,25582,2395],{"class":129},[112,25584,25585],{"class":114,"line":3778},[112,25586,3232],{"class":129},[112,25588,25589],{"class":114,"line":3787},[112,25590,147],{"emptyLinePlaceholder":146},[112,25592,25593,25595,25598,25600,25602,25604,25606,25608,25610,25612,25614,25616,25618,25620],{"class":114,"line":3809},[112,25594,10270],{"class":125},[112,25596,25597],{"class":163}," remove",[112,25599,425],{"class":129},[112,25601,17348],{"class":222},[112,25603,2243],{"class":125},[112,25605,17353],{"class":156},[112,25607,10186],{"class":129},[112,25609,2243],{"class":125},[112,25611,10289],{"class":163},[112,25613,17151],{"class":129},[112,25615,10811],{"class":222},[112,25617,2243],{"class":125},[112,25619,10478],{"class":156},[112,25621,17160],{"class":129},[112,25623,25624,25626,25628,25630,25632,25635,25637,25639,25641,25643,25645],{"class":114,"line":3827},[112,25625,3385],{"class":125},[112,25627,10318],{"class":156},[112,25629,25388],{"class":129},[112,25631,6609],{"class":163},[112,25633,25634],{"class":129},"(id).",[112,25636,3686],{"class":163},[112,25638,219],{"class":129},[112,25640,6950],{"class":222},[112,25642,226],{"class":129},[112,25644,229],{"class":125},[112,25646,1294],{"class":129},[112,25648,25649,25651,25653,25655],{"class":114,"line":3834},[112,25650,3519],{"class":125},[112,25652,232],{"class":125},[112,25654,10761],{"class":163},[112,25656,2304],{"class":129},[112,25658,25659,25662,25664,25666,25668,25670,25672,25675],{"class":114,"line":3848},[112,25660,25661],{"class":136},"        `[${",[112,25663,6950],{"class":129},[112,25665,2124],{"class":136},[112,25667,10811],{"class":129},[112,25669,25547],{"class":136},[112,25671,17348],{"class":129},[112,25673,25674],{"class":136},"}」の削除に失敗しました。`",[112,25676,179],{"class":129},[112,25678,25679],{"class":114,"line":3858},[112,25680,10820],{"class":129},[112,25682,25683],{"class":114,"line":3863},[112,25684,10257],{"class":129},[112,25686,25687],{"class":114,"line":3874},[112,25688,147],{"emptyLinePlaceholder":146},[112,25690,25691,25693,25695,25697,25699,25702],{"class":114,"line":3879},[112,25692,3973],{"class":125},[112,25694,11332],{"class":129},[112,25696,25575],{"class":136},[112,25698,17348],{"class":129},[112,25700,25701],{"class":136},"}」の削除に成功しました。`",[112,25703,2395],{"class":129},[112,25705,25706],{"class":114,"line":3884},[112,25707,3232],{"class":129},[112,25709,25710],{"class":114,"line":3907},[112,25711,1452],{"class":129},[83,25713,25715],{"id":25714},"controller実装","Controller実装",[103,25717,25719],{"className":105,"code":25718,"language":107,"meta":108,"style":108},"\u002F\u002F src\u002Fusers\u002Fusers.controller.ts\nimport {\n  Controller,\n  Get,\n  Post,\n  Body,\n  Patch,\n  Param,\n  Delete,\n} from '@nestjs\u002Fcommon'\nimport { UsersService } from '.\u002Fusers.service'\nimport { CreateUserDto } from '.\u002Fdto\u002Fcreate-user.dto'\nimport { UpdateUserDto } from '.\u002Fdto\u002Fupdate-user.dto'\nimport { User } from '.\u002Fentities\u002Fuser.entity'\n\n@Controller('users')\nexport class UsersController {\n  constructor(private readonly usersService: UsersService) {}\n\n  @Post()\n  async create(@Body() createUserDto: CreateUserDto): Promise\u003C{ message: string }> {\n    return await this.usersService.create(createUserDto)\n  }\n\n  @Get()\n  async findAll(): Promise\u003CUser[]> {\n    return await this.usersService.findAll()\n  }\n\n  @Get(':id')\n  async findOne(@Param('id') id: string): Promise\u003CUser> {\n    return await this.usersService.findOne(+id)\n  }\n\n  @Patch(':id')\n  async update(\n    @Param('id') id: string,\n    @Body() updateUserDto: UpdateUserDto,\n  ): Promise\u003C{ message: string }> {\n    return await this.usersService.update(+id, updateUserDto)\n  }\n\n  @Delete(':id')\n  async remove(@Param('id') id: string): Promise\u003C{ message: string }> {\n    return await this.usersService.remove(+id)\n  }\n}\n",[90,25720,25721,25726,25732,25736,25740,25744,25748,25753,25757,25761,25769,25779,25789,25799,25809,25813,25825,25835,25854,25858,25866,25900,25916,25920,25924,25932,25950,25965,25969,25973,25986,26020,26039,26043,26047,26060,26068,26088,26104,26122,26141,26145,26149,26161,26199,26218,26222],{"__ignoreMap":108},[112,25722,25723],{"class":114,"line":115},[112,25724,25725],{"class":118},"\u002F\u002F src\u002Fusers\u002Fusers.controller.ts\n",[112,25727,25728,25730],{"class":114,"line":122},[112,25729,126],{"class":125},[112,25731,1294],{"class":129},[112,25733,25734],{"class":114,"line":143},[112,25735,10899],{"class":129},[112,25737,25738],{"class":114,"line":150},[112,25739,10909],{"class":129},[112,25741,25742],{"class":114,"line":170},[112,25743,10919],{"class":129},[112,25745,25746],{"class":114,"line":182},[112,25747,10894],{"class":129},[112,25749,25750],{"class":114,"line":193},[112,25751,25752],{"class":129},"  Patch,\n",[112,25754,25755],{"class":114,"line":205},[112,25756,10914],{"class":129},[112,25758,25759],{"class":114,"line":241},[112,25760,10904],{"class":129},[112,25762,25763,25765,25767],{"class":114,"line":247},[112,25764,10929],{"class":129},[112,25766,133],{"class":125},[112,25768,9868],{"class":136},[112,25770,25771,25773,25775,25777],{"class":114,"line":351},[112,25772,126],{"class":125},[112,25774,25025],{"class":129},[112,25776,133],{"class":125},[112,25778,25030],{"class":136},[112,25780,25781,25783,25785,25787],{"class":114,"line":361},[112,25782,126],{"class":125},[112,25784,17037],{"class":129},[112,25786,133],{"class":125},[112,25788,25163],{"class":136},[112,25790,25791,25793,25795,25797],{"class":114,"line":367},[112,25792,126],{"class":125},[112,25794,25170],{"class":129},[112,25796,133],{"class":125},[112,25798,25175],{"class":136},[112,25800,25801,25803,25805,25807],{"class":114,"line":373},[112,25802,126],{"class":125},[112,25804,17025],{"class":129},[112,25806,133],{"class":125},[112,25808,25053],{"class":136},[112,25810,25811],{"class":114,"line":379},[112,25812,147],{"emptyLinePlaceholder":146},[112,25814,25815,25817,25819,25821,25823],{"class":114,"line":1302},[112,25816,9901],{"class":129},[112,25818,10878],{"class":163},[112,25820,425],{"class":129},[112,25822,11037],{"class":136},[112,25824,431],{"class":129},[112,25826,25827,25829,25831,25833],{"class":114,"line":1502},[112,25828,288],{"class":125},[112,25830,9931],{"class":125},[112,25832,11048],{"class":163},[112,25834,1294],{"class":129},[112,25836,25837,25839,25841,25843,25845,25848,25850,25852],{"class":114,"line":1507},[112,25838,10128],{"class":125},[112,25840,425],{"class":129},[112,25842,10133],{"class":125},[112,25844,10097],{"class":125},[112,25846,25847],{"class":222}," usersService",[112,25849,2243],{"class":125},[112,25851,17081],{"class":163},[112,25853,11070],{"class":129},[112,25855,25856],{"class":114,"line":1512},[112,25857,147],{"emptyLinePlaceholder":146},[112,25859,25860,25862,25864],{"class":114,"line":1518},[112,25861,11079],{"class":129},[112,25863,11082],{"class":163},[112,25865,630],{"class":129},[112,25867,25868,25870,25872,25874,25876,25878,25880,25882,25884,25886,25888,25890,25892,25894,25896,25898],{"class":114,"line":1524},[112,25869,10270],{"class":125},[112,25871,6835],{"class":163},[112,25873,11093],{"class":129},[112,25875,11096],{"class":163},[112,25877,1392],{"class":129},[112,25879,17138],{"class":222},[112,25881,2243],{"class":125},[112,25883,10957],{"class":163},[112,25885,10186],{"class":129},[112,25887,2243],{"class":125},[112,25889,10289],{"class":163},[112,25891,17151],{"class":129},[112,25893,10811],{"class":222},[112,25895,2243],{"class":125},[112,25897,10478],{"class":156},[112,25899,17160],{"class":129},[112,25901,25902,25904,25906,25908,25911,25913],{"class":114,"line":1530},[112,25903,3973],{"class":125},[112,25905,419],{"class":125},[112,25907,10318],{"class":156},[112,25909,25910],{"class":129},".usersService.",[112,25912,14070],{"class":163},[112,25914,25915],{"class":129},"(createUserDto)\n",[112,25917,25918],{"class":114,"line":1536},[112,25919,3232],{"class":129},[112,25921,25922],{"class":114,"line":1542},[112,25923,147],{"emptyLinePlaceholder":146},[112,25925,25926,25928,25930],{"class":114,"line":2402},[112,25927,11079],{"class":129},[112,25929,11136],{"class":163},[112,25931,630],{"class":129},[112,25933,25934,25936,25938,25940,25942,25944,25946,25948],{"class":114,"line":2407},[112,25935,10270],{"class":125},[112,25937,17302],{"class":163},[112,25939,13899],{"class":129},[112,25941,2243],{"class":125},[112,25943,10289],{"class":163},[112,25945,6944],{"class":129},[112,25947,10489],{"class":163},[112,25949,17315],{"class":129},[112,25951,25952,25954,25956,25958,25960,25963],{"class":114,"line":2413},[112,25953,3973],{"class":125},[112,25955,419],{"class":125},[112,25957,10318],{"class":156},[112,25959,25910],{"class":129},[112,25961,25962],{"class":163},"findAll",[112,25964,630],{"class":129},[112,25966,25967],{"class":114,"line":2446},[112,25968,3232],{"class":129},[112,25970,25971],{"class":114,"line":2451},[112,25972,147],{"emptyLinePlaceholder":146},[112,25974,25975,25977,25979,25981,25984],{"class":114,"line":2464},[112,25976,11079],{"class":129},[112,25978,11136],{"class":163},[112,25980,425],{"class":129},[112,25982,25983],{"class":136},"':id'",[112,25985,431],{"class":129},[112,25987,25988,25990,25992,25994,25996,25998,26000,26002,26004,26006,26008,26010,26012,26014,26016,26018],{"class":114,"line":2481},[112,25989,10270],{"class":125},[112,25991,17343],{"class":163},[112,25993,11093],{"class":129},[112,25995,11155],{"class":163},[112,25997,425],{"class":129},[112,25999,16123],{"class":136},[112,26001,226],{"class":129},[112,26003,17348],{"class":222},[112,26005,2243],{"class":125},[112,26007,10478],{"class":156},[112,26009,10186],{"class":129},[112,26011,2243],{"class":125},[112,26013,10289],{"class":163},[112,26015,6944],{"class":129},[112,26017,10489],{"class":163},[112,26019,10297],{"class":129},[112,26021,26022,26024,26026,26028,26030,26032,26034,26036],{"class":114,"line":2486},[112,26023,3973],{"class":125},[112,26025,419],{"class":125},[112,26027,10318],{"class":156},[112,26029,25910],{"class":129},[112,26031,17179],{"class":163},[112,26033,425],{"class":129},[112,26035,3708],{"class":125},[112,26037,26038],{"class":129},"id)\n",[112,26040,26041],{"class":114,"line":3229},[112,26042,3232],{"class":129},[112,26044,26045],{"class":114,"line":3235},[112,26046,147],{"emptyLinePlaceholder":146},[112,26048,26049,26051,26054,26056,26058],{"class":114,"line":3657},[112,26050,11079],{"class":129},[112,26052,26053],{"class":163},"Patch",[112,26055,425],{"class":129},[112,26057,25983],{"class":136},[112,26059,431],{"class":129},[112,26061,26062,26064,26066],{"class":114,"line":3662},[112,26063,10270],{"class":125},[112,26065,25457],{"class":163},[112,26067,2304],{"class":129},[112,26069,26070,26072,26074,26076,26078,26080,26082,26084,26086],{"class":114,"line":3667},[112,26071,11218],{"class":129},[112,26073,11155],{"class":163},[112,26075,425],{"class":129},[112,26077,16123],{"class":136},[112,26079,226],{"class":129},[112,26081,17348],{"class":222},[112,26083,2243],{"class":125},[112,26085,10478],{"class":156},[112,26087,179],{"class":129},[112,26089,26090,26092,26094,26096,26098,26100,26102],{"class":114,"line":3680},[112,26091,11218],{"class":129},[112,26093,11096],{"class":163},[112,26095,1392],{"class":129},[112,26097,25470],{"class":222},[112,26099,2243],{"class":125},[112,26101,11000],{"class":163},[112,26103,179],{"class":129},[112,26105,26106,26108,26110,26112,26114,26116,26118,26120],{"class":114,"line":3692},[112,26107,11797],{"class":129},[112,26109,2243],{"class":125},[112,26111,10289],{"class":163},[112,26113,17151],{"class":129},[112,26115,10811],{"class":222},[112,26117,2243],{"class":125},[112,26119,10478],{"class":156},[112,26121,17160],{"class":129},[112,26123,26124,26126,26128,26130,26132,26134,26136,26138],{"class":114,"line":3716},[112,26125,3973],{"class":125},[112,26127,419],{"class":125},[112,26129,10318],{"class":156},[112,26131,25910],{"class":129},[112,26133,25505],{"class":163},[112,26135,425],{"class":129},[112,26137,3708],{"class":125},[112,26139,26140],{"class":129},"id, updateUserDto)\n",[112,26142,26143],{"class":114,"line":3737},[112,26144,3232],{"class":129},[112,26146,26147],{"class":114,"line":3742},[112,26148,147],{"emptyLinePlaceholder":146},[112,26150,26151,26153,26155,26157,26159],{"class":114,"line":3747},[112,26152,11079],{"class":129},[112,26154,11283],{"class":163},[112,26156,425],{"class":129},[112,26158,25983],{"class":136},[112,26160,431],{"class":129},[112,26162,26163,26165,26167,26169,26171,26173,26175,26177,26179,26181,26183,26185,26187,26189,26191,26193,26195,26197],{"class":114,"line":3752},[112,26164,10270],{"class":125},[112,26166,25597],{"class":163},[112,26168,11093],{"class":129},[112,26170,11155],{"class":163},[112,26172,425],{"class":129},[112,26174,16123],{"class":136},[112,26176,226],{"class":129},[112,26178,17348],{"class":222},[112,26180,2243],{"class":125},[112,26182,10478],{"class":156},[112,26184,10186],{"class":129},[112,26186,2243],{"class":125},[112,26188,10289],{"class":163},[112,26190,17151],{"class":129},[112,26192,10811],{"class":222},[112,26194,2243],{"class":125},[112,26196,10478],{"class":156},[112,26198,17160],{"class":129},[112,26200,26201,26203,26205,26207,26209,26212,26214,26216],{"class":114,"line":3758},[112,26202,3973],{"class":125},[112,26204,419],{"class":125},[112,26206,10318],{"class":156},[112,26208,25910],{"class":129},[112,26210,26211],{"class":163},"remove",[112,26213,425],{"class":129},[112,26215,3708],{"class":125},[112,26217,26038],{"class":129},[112,26219,26220],{"class":114,"line":3778},[112,26221,3232],{"class":129},[112,26223,26224],{"class":114,"line":3787},[112,26225,1452],{"class":129},[11,26227,26229],{"id":26228},"apiのテスト","APIのテスト",[83,26231,26232],{"id":26232},"登録",[103,26234,26236],{"className":771,"code":26235,"language":773,"meta":108,"style":108},"curl -X POST -H \"Content-Type:application\u002Fjson\" \\\n  http:\u002F\u002Flocalhost:3001\u002Fusers \\\n  -d '{\"name\":\"サンプル太郎\"}'\n\n# {\"message\":\"アカウントの登録に成功しました\"}\n",[90,26237,26238,26254,26261,26269,26273],{"__ignoreMap":108},[112,26239,26240,26242,26244,26246,26249,26252],{"class":114,"line":115},[112,26241,14330],{"class":163},[112,26243,14463],{"class":156},[112,26245,14339],{"class":136},[112,26247,26248],{"class":156}," -H",[112,26250,26251],{"class":136}," \"Content-Type:application\u002Fjson\"",[112,26253,8535],{"class":156},[112,26255,26256,26259],{"class":114,"line":122},[112,26257,26258],{"class":136},"  http:\u002F\u002Flocalhost:3001\u002Fusers",[112,26260,8535],{"class":156},[112,26262,26263,26266],{"class":114,"line":143},[112,26264,26265],{"class":156},"  -d",[112,26267,26268],{"class":136}," '{\"name\":\"サンプル太郎\"}'\n",[112,26270,26271],{"class":114,"line":150},[112,26272,147],{"emptyLinePlaceholder":146},[112,26274,26275],{"class":114,"line":170},[112,26276,26277],{"class":118},"# {\"message\":\"アカウントの登録に成功しました\"}\n",[83,26279,26280],{"id":26280},"全件取得",[103,26282,26284],{"className":771,"code":26283,"language":773,"meta":108,"style":108},"curl http:\u002F\u002Flocalhost:3001\u002Fusers\n\n# [{\"name\":\"サンプル太郎\",\"id\":1}]\n",[90,26285,26286,26293,26297],{"__ignoreMap":108},[112,26287,26288,26290],{"class":114,"line":115},[112,26289,14330],{"class":163},[112,26291,26292],{"class":136}," http:\u002F\u002Flocalhost:3001\u002Fusers\n",[112,26294,26295],{"class":114,"line":122},[112,26296,147],{"emptyLinePlaceholder":146},[112,26298,26299],{"class":114,"line":143},[112,26300,26301],{"class":118},"# [{\"name\":\"サンプル太郎\",\"id\":1}]\n",[83,26303,26304],{"id":26304},"更新",[103,26306,26308],{"className":771,"code":26307,"language":773,"meta":108,"style":108},"curl -X PATCH -H \"Content-Type:application\u002Fjson\" \\\n  http:\u002F\u002Flocalhost:3001\u002Fusers\u002F1 \\\n  -d '{\"name\":\"更新したよ\"}'\n\n# {\"message\":\"アカウントID「1」の更新に成功しました。\"}\n",[90,26309,26310,26325,26332,26339,26343],{"__ignoreMap":108},[112,26311,26312,26314,26316,26319,26321,26323],{"class":114,"line":115},[112,26313,14330],{"class":163},[112,26315,14463],{"class":156},[112,26317,26318],{"class":136}," PATCH",[112,26320,26248],{"class":156},[112,26322,26251],{"class":136},[112,26324,8535],{"class":156},[112,26326,26327,26330],{"class":114,"line":122},[112,26328,26329],{"class":136},"  http:\u002F\u002Flocalhost:3001\u002Fusers\u002F1",[112,26331,8535],{"class":156},[112,26333,26334,26336],{"class":114,"line":143},[112,26335,26265],{"class":156},[112,26337,26338],{"class":136}," '{\"name\":\"更新したよ\"}'\n",[112,26340,26341],{"class":114,"line":150},[112,26342,147],{"emptyLinePlaceholder":146},[112,26344,26345],{"class":114,"line":170},[112,26346,26347],{"class":118},"# {\"message\":\"アカウントID「1」の更新に成功しました。\"}\n",[83,26349,26350],{"id":26350},"個別取得",[103,26352,26354],{"className":771,"code":26353,"language":773,"meta":108,"style":108},"curl http:\u002F\u002Flocalhost:3001\u002Fusers\u002F1\n\n# {\"name\":\"更新したよ\",\"id\":1}\n",[90,26355,26356,26363,26367],{"__ignoreMap":108},[112,26357,26358,26360],{"class":114,"line":115},[112,26359,14330],{"class":163},[112,26361,26362],{"class":136}," http:\u002F\u002Flocalhost:3001\u002Fusers\u002F1\n",[112,26364,26365],{"class":114,"line":122},[112,26366,147],{"emptyLinePlaceholder":146},[112,26368,26369],{"class":114,"line":143},[112,26370,26371],{"class":118},"# {\"name\":\"更新したよ\",\"id\":1}\n",[83,26373,26374],{"id":26374},"削除",[103,26376,26378],{"className":771,"code":26377,"language":773,"meta":108,"style":108},"curl -X DELETE http:\u002F\u002Flocalhost:3001\u002Fusers\u002F1\n\n# {\"message\":\"アカウントID「1」の削除に成功しました。\"}\n",[90,26379,26380,26391,26395],{"__ignoreMap":108},[112,26381,26382,26384,26386,26389],{"class":114,"line":115},[112,26383,14330],{"class":163},[112,26385,14463],{"class":156},[112,26387,26388],{"class":136}," DELETE",[112,26390,26362],{"class":136},[112,26392,26393],{"class":114,"line":122},[112,26394,147],{"emptyLinePlaceholder":146},[112,26396,26397],{"class":114,"line":143},[112,26398,26399],{"class":118},"# {\"message\":\"アカウントID「1」の削除に成功しました。\"}\n",[11,26401,919],{"id":919},[15,26403,26404],{},"NestJS V.9とTypeORM 0.3を使った、最新のREST API構築方法を解説しました。",[15,26406,26407],{},[27,26408,26409],{},"実装したもの：",[31,26411,26412,26415,26418,26421],{},[34,26413,26414],{},"TypeORMを使ったEntity定義",[34,26416,26417],{},"マイグレーション管理",[34,26419,26420],{},"class-validatorを使ったバリデーション",[34,26422,26423],{},"CRUD操作の完全実装",[15,26425,26426],{},[27,26427,18794],{},[31,26429,26430,26433,26436,26439],{},[34,26431,26432],{},"TypeORM 0.3系では、Repository APIが変更されている",[34,26434,26435],{},"本番環境では環境変数で設定を管理",[34,26437,26438],{},"グローバルバリデーションで一貫したエラーハンドリング",[34,26440,26441],{},"マイグレーションでスキーマをバージョン管理",[15,26443,26444],{},"この実装パターンは、スケーラブルなAPIの基礎として活用できます。",[944,26446,26447],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":108,"searchDepth":122,"depth":122,"links":26449},[26450,26451,26452,26453,26458,26459,26465,26466,26470,26475,26480,26487],{"id":13,"depth":122,"text":13},{"id":23687,"depth":122,"text":23687},{"id":23739,"depth":122,"text":23740},{"id":14707,"depth":122,"text":14707,"children":26454},[26455,26456,26457],{"id":23769,"depth":143,"text":23770},{"id":23852,"depth":143,"text":23853},{"id":23946,"depth":143,"text":23946},{"id":23999,"depth":122,"text":24000},{"id":24126,"depth":122,"text":24127,"children":26460},[26461,26462,26463,26464],{"id":24130,"depth":143,"text":24130},{"id":24164,"depth":143,"text":24165},{"id":24267,"depth":143,"text":24268},{"id":15669,"depth":143,"text":15670},{"id":16017,"depth":122,"text":16018},{"id":24694,"depth":122,"text":24694,"children":26467},[26468,26469],{"id":24697,"depth":143,"text":24697},{"id":16625,"depth":143,"text":16625},{"id":24762,"depth":122,"text":24762,"children":26471},[26472,26473,26474],{"id":24765,"depth":143,"text":24130},{"id":24784,"depth":143,"text":24785},{"id":24865,"depth":143,"text":24865},{"id":24983,"depth":122,"text":24984,"children":26476},[26477,26478,26479],{"id":24987,"depth":143,"text":24988},{"id":16949,"depth":143,"text":16950},{"id":25714,"depth":143,"text":25715},{"id":26228,"depth":122,"text":26229,"children":26481},[26482,26483,26484,26485,26486],{"id":26232,"depth":143,"text":26232},{"id":26280,"depth":143,"text":26280},{"id":26304,"depth":143,"text":26304},{"id":26350,"depth":143,"text":26350},{"id":26374,"depth":143,"text":26374},{"id":919,"depth":122,"text":919},"2021-12-19","NestJS V.9とTypeORM 0.3を使った最新のREST API構築方法を解説します。マイグレーション管理、バリデーション、CRUD操作の実装を含む実践的なガイドです。",{"tags":26491},[107,12798,26492,26493],"typeorm","api","\u002Fblog\u002Fnestjs-typeorm-crud-implementation",{"title":23669,"description":26489},"blog\u002Fnestjs-typeorm-crud-implementation","ecXoiZerzL-q8syvb0omwfHgIX7qlaHxA2SB5NKGYCk",{"id":26499,"title":26500,"body":26501,"date":28640,"description":28641,"extension":962,"meta":28642,"navigation":146,"path":28645,"seo":28646,"stem":28647,"__hash__":28648},"blog\u002Fblog\u002Fdayjs-japanese-locale.md","Day.jsで日本語ロケールとプラグインを設定する",{"type":8,"value":26502,"toc":28616},[26503,26507,26515,26519,26545,26548,26551,26554,26639,26643,26761,26764,26767,26771,26929,26933,27083,27087,27233,27237,27381,27385,27554,27556,27559,27789,27792,27795,27961,27964,28082,28085,28345,28349,28552,28554,28557,28586,28589,28591,28613],[11,26504,26506],{"id":26505},"dayjsとは","Day.jsとは",[15,26508,26509,26514],{},[759,26510,26513],{"href":26511,"rel":26512},"https:\u002F\u002Fday.js.org\u002F",[763],"Day.js","は、日付・時刻を扱うための軽量なJavaScriptライブラリです。",[15,26516,26517],{},[27,26518,2608],{},[31,26520,26521,26527,26533,26539],{},[34,26522,26523,26526],{},[27,26524,26525],{},"軽量"," わずか2KBのサイズ",[34,26528,26529,26532],{},[27,26530,26531],{},"Moment.js互換"," APIが似ているため移行が容易",[34,26534,26535,26538],{},[27,26536,26537],{},"プラグインシステム"," 必要な機能だけを追加可能",[34,26540,26541,26544],{},[27,26542,26543],{},"イミュータブル"," 元のオブジェクトを変更しない",[11,26546,26547],{"id":26547},"ロケール設定",[15,26549,26550],{},"Day.jsのデフォルトロケールは英語（en）です。日本語表示にするには、ロケールを設定してください。",[83,26552,26553],{"id":26553},"基本的な設定",[103,26555,26559],{"className":26556,"code":26557,"language":26558,"meta":108,"style":108},"language-ts shiki shiki-themes github-light github-dark","import dayjs from 'dayjs'\nimport 'dayjs\u002Flocale\u002Fja'\n\n\u002F\u002F 日本語ロケールを設定\ndayjs.locale('ja')\n\nconsole.log(dayjs().format('YYYY年M月D日(ddd)'))\n\u002F\u002F 出力: 2021年11月29日(月)\n","ts",[90,26560,26561,26573,26580,26584,26589,26604,26608,26634],{"__ignoreMap":108},[112,26562,26563,26565,26568,26570],{"class":114,"line":115},[112,26564,126],{"class":125},[112,26566,26567],{"class":129}," dayjs ",[112,26569,133],{"class":125},[112,26571,26572],{"class":136}," 'dayjs'\n",[112,26574,26575,26577],{"class":114,"line":122},[112,26576,126],{"class":125},[112,26578,26579],{"class":136}," 'dayjs\u002Flocale\u002Fja'\n",[112,26581,26582],{"class":114,"line":143},[112,26583,147],{"emptyLinePlaceholder":146},[112,26585,26586],{"class":114,"line":150},[112,26587,26588],{"class":118},"\u002F\u002F 日本語ロケールを設定\n",[112,26590,26591,26594,26597,26599,26602],{"class":114,"line":170},[112,26592,26593],{"class":129},"dayjs.",[112,26595,26596],{"class":163},"locale",[112,26598,425],{"class":129},[112,26600,26601],{"class":136},"'ja'",[112,26603,431],{"class":129},[112,26605,26606],{"class":114,"line":182},[112,26607,147],{"emptyLinePlaceholder":146},[112,26609,26610,26613,26616,26618,26621,26623,26626,26628,26631],{"class":114,"line":193},[112,26611,26612],{"class":129},"console.",[112,26614,26615],{"class":163},"log",[112,26617,425],{"class":129},[112,26619,26620],{"class":163},"dayjs",[112,26622,213],{"class":129},[112,26624,26625],{"class":163},"format",[112,26627,425],{"class":129},[112,26629,26630],{"class":136},"'YYYY年M月D日(ddd)'",[112,26632,26633],{"class":129},"))\n",[112,26635,26636],{"class":114,"line":205},[112,26637,26638],{"class":118},"\u002F\u002F 出力: 2021年11月29日(月)\n",[83,26640,26642],{"id":26641},"グローバル-vs-ローカル設定","グローバル vs ローカル設定",[103,26644,26646],{"className":26556,"code":26645,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\nimport 'dayjs\u002Flocale\u002Fja'\nimport 'dayjs\u002Flocale\u002Fen'\n\n\u002F\u002F グローバルに日本語を設定\ndayjs.locale('ja')\nconsole.log(dayjs().format('MMMM'))\n\u002F\u002F 出力: 11月\n\n\u002F\u002F 特定のインスタンスだけ英語にする\nconsole.log(dayjs().locale('en').format('MMMM'))\n\u002F\u002F 出力: November\n",[90,26647,26648,26658,26664,26671,26675,26680,26692,26713,26718,26722,26727,26756],{"__ignoreMap":108},[112,26649,26650,26652,26654,26656],{"class":114,"line":115},[112,26651,126],{"class":125},[112,26653,26567],{"class":129},[112,26655,133],{"class":125},[112,26657,26572],{"class":136},[112,26659,26660,26662],{"class":114,"line":122},[112,26661,126],{"class":125},[112,26663,26579],{"class":136},[112,26665,26666,26668],{"class":114,"line":143},[112,26667,126],{"class":125},[112,26669,26670],{"class":136}," 'dayjs\u002Flocale\u002Fen'\n",[112,26672,26673],{"class":114,"line":150},[112,26674,147],{"emptyLinePlaceholder":146},[112,26676,26677],{"class":114,"line":170},[112,26678,26679],{"class":118},"\u002F\u002F グローバルに日本語を設定\n",[112,26681,26682,26684,26686,26688,26690],{"class":114,"line":182},[112,26683,26593],{"class":129},[112,26685,26596],{"class":163},[112,26687,425],{"class":129},[112,26689,26601],{"class":136},[112,26691,431],{"class":129},[112,26693,26694,26696,26698,26700,26702,26704,26706,26708,26711],{"class":114,"line":193},[112,26695,26612],{"class":129},[112,26697,26615],{"class":163},[112,26699,425],{"class":129},[112,26701,26620],{"class":163},[112,26703,213],{"class":129},[112,26705,26625],{"class":163},[112,26707,425],{"class":129},[112,26709,26710],{"class":136},"'MMMM'",[112,26712,26633],{"class":129},[112,26714,26715],{"class":114,"line":205},[112,26716,26717],{"class":118},"\u002F\u002F 出力: 11月\n",[112,26719,26720],{"class":114,"line":241},[112,26721,147],{"emptyLinePlaceholder":146},[112,26723,26724],{"class":114,"line":247},[112,26725,26726],{"class":118},"\u002F\u002F 特定のインスタンスだけ英語にする\n",[112,26728,26729,26731,26733,26735,26737,26739,26741,26743,26746,26748,26750,26752,26754],{"class":114,"line":351},[112,26730,26612],{"class":129},[112,26732,26615],{"class":163},[112,26734,425],{"class":129},[112,26736,26620],{"class":163},[112,26738,213],{"class":129},[112,26740,26596],{"class":163},[112,26742,425],{"class":129},[112,26744,26745],{"class":136},"'en'",[112,26747,610],{"class":129},[112,26749,26625],{"class":163},[112,26751,425],{"class":129},[112,26753,26710],{"class":136},[112,26755,26633],{"class":129},[112,26757,26758],{"class":114,"line":361},[112,26759,26760],{"class":118},"\u002F\u002F 出力: November\n",[11,26762,26763],{"id":26763},"よく使うプラグイン",[15,26765,26766],{},"Day.jsはプラグインシステムで機能を拡張できます。",[83,26768,26770],{"id":26769},"_1-minmax-最大最小値の取得","1. MinMax - 最大・最小値の取得",[103,26772,26774],{"className":26556,"code":26773,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\nimport minMax from 'dayjs\u002Fplugin\u002FminMax'\n\ndayjs.extend(minMax)\n\nconst dates = [\n  dayjs('2021-11-01'),\n  dayjs('2021-11-15'),\n  dayjs('2021-11-30'),\n]\n\nconsole.log(dayjs.max(dates).format('YYYY-MM-DD'))\n\u002F\u002F 出力: 2021-11-30\n\nconsole.log(dayjs.min(dates).format('YYYY-MM-DD'))\n\u002F\u002F 出力: 2021-11-01\n",[90,26775,26776,26786,26798,26802,26812,26816,26828,26840,26851,26862,26866,26870,26894,26899,26903,26924],{"__ignoreMap":108},[112,26777,26778,26780,26782,26784],{"class":114,"line":115},[112,26779,126],{"class":125},[112,26781,26567],{"class":129},[112,26783,133],{"class":125},[112,26785,26572],{"class":136},[112,26787,26788,26790,26793,26795],{"class":114,"line":122},[112,26789,126],{"class":125},[112,26791,26792],{"class":129}," minMax ",[112,26794,133],{"class":125},[112,26796,26797],{"class":136}," 'dayjs\u002Fplugin\u002FminMax'\n",[112,26799,26800],{"class":114,"line":143},[112,26801,147],{"emptyLinePlaceholder":146},[112,26803,26804,26806,26809],{"class":114,"line":150},[112,26805,26593],{"class":129},[112,26807,26808],{"class":163},"extend",[112,26810,26811],{"class":129},"(minMax)\n",[112,26813,26814],{"class":114,"line":170},[112,26815,147],{"emptyLinePlaceholder":146},[112,26817,26818,26820,26823,26825],{"class":114,"line":182},[112,26819,153],{"class":125},[112,26821,26822],{"class":156}," dates",[112,26824,160],{"class":125},[112,26826,26827],{"class":129}," [\n",[112,26829,26830,26833,26835,26838],{"class":114,"line":193},[112,26831,26832],{"class":163},"  dayjs",[112,26834,425],{"class":129},[112,26836,26837],{"class":136},"'2021-11-01'",[112,26839,15842],{"class":129},[112,26841,26842,26844,26846,26849],{"class":114,"line":205},[112,26843,26832],{"class":163},[112,26845,425],{"class":129},[112,26847,26848],{"class":136},"'2021-11-15'",[112,26850,15842],{"class":129},[112,26852,26853,26855,26857,26860],{"class":114,"line":241},[112,26854,26832],{"class":163},[112,26856,425],{"class":129},[112,26858,26859],{"class":136},"'2021-11-30'",[112,26861,15842],{"class":129},[112,26863,26864],{"class":114,"line":247},[112,26865,19410],{"class":129},[112,26867,26868],{"class":114,"line":351},[112,26869,147],{"emptyLinePlaceholder":146},[112,26871,26872,26874,26876,26879,26882,26885,26887,26889,26892],{"class":114,"line":361},[112,26873,26612],{"class":129},[112,26875,26615],{"class":163},[112,26877,26878],{"class":129},"(dayjs.",[112,26880,26881],{"class":163},"max",[112,26883,26884],{"class":129},"(dates).",[112,26886,26625],{"class":163},[112,26888,425],{"class":129},[112,26890,26891],{"class":136},"'YYYY-MM-DD'",[112,26893,26633],{"class":129},[112,26895,26896],{"class":114,"line":367},[112,26897,26898],{"class":118},"\u002F\u002F 出力: 2021-11-30\n",[112,26900,26901],{"class":114,"line":373},[112,26902,147],{"emptyLinePlaceholder":146},[112,26904,26905,26907,26909,26911,26914,26916,26918,26920,26922],{"class":114,"line":379},[112,26906,26612],{"class":129},[112,26908,26615],{"class":163},[112,26910,26878],{"class":129},[112,26912,26913],{"class":163},"min",[112,26915,26884],{"class":129},[112,26917,26625],{"class":163},[112,26919,425],{"class":129},[112,26921,26891],{"class":136},[112,26923,26633],{"class":129},[112,26925,26926],{"class":114,"line":1302},[112,26927,26928],{"class":118},"\u002F\u002F 出力: 2021-11-01\n",[83,26930,26932],{"id":26931},"_2-timezone-タイムゾーン対応","2. Timezone - タイムゾーン対応",[103,26934,26936],{"className":26556,"code":26935,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\nimport utc from 'dayjs\u002Fplugin\u002Futc'\nimport timezone from 'dayjs\u002Fplugin\u002Ftimezone'\n\ndayjs.extend(utc)\ndayjs.extend(timezone)\n\n\u002F\u002F 日本時間を明示的に指定\nconst jst = dayjs.tz('2021-11-29 12:00', 'Asia\u002FTokyo')\nconsole.log(jst.format())\n\u002F\u002F 出力: 2021-11-29T12:00:00+09:00\n\n\u002F\u002F 別のタイムゾーンに変換\nconsole.log(jst.tz('America\u002FNew_York').format())\n\u002F\u002F 出力: 2021-11-28T22:00:00-05:00\n",[90,26937,26938,26948,26960,26972,26976,26985,26994,26998,27003,27030,27043,27048,27052,27057,27078],{"__ignoreMap":108},[112,26939,26940,26942,26944,26946],{"class":114,"line":115},[112,26941,126],{"class":125},[112,26943,26567],{"class":129},[112,26945,133],{"class":125},[112,26947,26572],{"class":136},[112,26949,26950,26952,26955,26957],{"class":114,"line":122},[112,26951,126],{"class":125},[112,26953,26954],{"class":129}," utc ",[112,26956,133],{"class":125},[112,26958,26959],{"class":136}," 'dayjs\u002Fplugin\u002Futc'\n",[112,26961,26962,26964,26967,26969],{"class":114,"line":143},[112,26963,126],{"class":125},[112,26965,26966],{"class":129}," timezone ",[112,26968,133],{"class":125},[112,26970,26971],{"class":136}," 'dayjs\u002Fplugin\u002Ftimezone'\n",[112,26973,26974],{"class":114,"line":150},[112,26975,147],{"emptyLinePlaceholder":146},[112,26977,26978,26980,26982],{"class":114,"line":170},[112,26979,26593],{"class":129},[112,26981,26808],{"class":163},[112,26983,26984],{"class":129},"(utc)\n",[112,26986,26987,26989,26991],{"class":114,"line":182},[112,26988,26593],{"class":129},[112,26990,26808],{"class":163},[112,26992,26993],{"class":129},"(timezone)\n",[112,26995,26996],{"class":114,"line":193},[112,26997,147],{"emptyLinePlaceholder":146},[112,26999,27000],{"class":114,"line":205},[112,27001,27002],{"class":118},"\u002F\u002F 日本時間を明示的に指定\n",[112,27004,27005,27007,27010,27012,27015,27018,27020,27023,27025,27028],{"class":114,"line":241},[112,27006,153],{"class":125},[112,27008,27009],{"class":156}," jst",[112,27011,160],{"class":125},[112,27013,27014],{"class":129}," dayjs.",[112,27016,27017],{"class":163},"tz",[112,27019,425],{"class":129},[112,27021,27022],{"class":136},"'2021-11-29 12:00'",[112,27024,447],{"class":129},[112,27026,27027],{"class":136},"'Asia\u002FTokyo'",[112,27029,431],{"class":129},[112,27031,27032,27034,27036,27039,27041],{"class":114,"line":247},[112,27033,26612],{"class":129},[112,27035,26615],{"class":163},[112,27037,27038],{"class":129},"(jst.",[112,27040,26625],{"class":163},[112,27042,17821],{"class":129},[112,27044,27045],{"class":114,"line":351},[112,27046,27047],{"class":118},"\u002F\u002F 出力: 2021-11-29T12:00:00+09:00\n",[112,27049,27050],{"class":114,"line":361},[112,27051,147],{"emptyLinePlaceholder":146},[112,27053,27054],{"class":114,"line":367},[112,27055,27056],{"class":118},"\u002F\u002F 別のタイムゾーンに変換\n",[112,27058,27059,27061,27063,27065,27067,27069,27072,27074,27076],{"class":114,"line":373},[112,27060,26612],{"class":129},[112,27062,26615],{"class":163},[112,27064,27038],{"class":129},[112,27066,27017],{"class":163},[112,27068,425],{"class":129},[112,27070,27071],{"class":136},"'America\u002FNew_York'",[112,27073,610],{"class":129},[112,27075,26625],{"class":163},[112,27077,17821],{"class":129},[112,27079,27080],{"class":114,"line":379},[112,27081,27082],{"class":118},"\u002F\u002F 出力: 2021-11-28T22:00:00-05:00\n",[83,27084,27086],{"id":27085},"_3-relativetime-相対時間の表示","3. RelativeTime - 相対時間の表示",[103,27088,27090],{"className":26556,"code":27089,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\nimport relativeTime from 'dayjs\u002Fplugin\u002FrelativeTime'\nimport 'dayjs\u002Flocale\u002Fja'\n\ndayjs.extend(relativeTime)\ndayjs.locale('ja')\n\nconsole.log(dayjs().from(dayjs().add(7, 'day')))\n\u002F\u002F 出力: 7日後\n\nconsole.log(dayjs().from(dayjs().subtract(2, 'hour')))\n\u002F\u002F 出力: 2時間前\n",[90,27091,27092,27102,27114,27120,27124,27133,27145,27149,27185,27190,27194,27228],{"__ignoreMap":108},[112,27093,27094,27096,27098,27100],{"class":114,"line":115},[112,27095,126],{"class":125},[112,27097,26567],{"class":129},[112,27099,133],{"class":125},[112,27101,26572],{"class":136},[112,27103,27104,27106,27109,27111],{"class":114,"line":122},[112,27105,126],{"class":125},[112,27107,27108],{"class":129}," relativeTime ",[112,27110,133],{"class":125},[112,27112,27113],{"class":136}," 'dayjs\u002Fplugin\u002FrelativeTime'\n",[112,27115,27116,27118],{"class":114,"line":143},[112,27117,126],{"class":125},[112,27119,26579],{"class":136},[112,27121,27122],{"class":114,"line":150},[112,27123,147],{"emptyLinePlaceholder":146},[112,27125,27126,27128,27130],{"class":114,"line":170},[112,27127,26593],{"class":129},[112,27129,26808],{"class":163},[112,27131,27132],{"class":129},"(relativeTime)\n",[112,27134,27135,27137,27139,27141,27143],{"class":114,"line":182},[112,27136,26593],{"class":129},[112,27138,26596],{"class":163},[112,27140,425],{"class":129},[112,27142,26601],{"class":136},[112,27144,431],{"class":129},[112,27146,27147],{"class":114,"line":193},[112,27148,147],{"emptyLinePlaceholder":146},[112,27150,27151,27153,27155,27157,27159,27161,27163,27165,27167,27169,27172,27174,27177,27179,27182],{"class":114,"line":205},[112,27152,26612],{"class":129},[112,27154,26615],{"class":163},[112,27156,425],{"class":129},[112,27158,26620],{"class":163},[112,27160,213],{"class":129},[112,27162,133],{"class":163},[112,27164,425],{"class":129},[112,27166,26620],{"class":163},[112,27168,213],{"class":129},[112,27170,27171],{"class":163},"add",[112,27173,425],{"class":129},[112,27175,27176],{"class":156},"7",[112,27178,447],{"class":129},[112,27180,27181],{"class":136},"'day'",[112,27183,27184],{"class":129},")))\n",[112,27186,27187],{"class":114,"line":241},[112,27188,27189],{"class":118},"\u002F\u002F 出力: 7日後\n",[112,27191,27192],{"class":114,"line":247},[112,27193,147],{"emptyLinePlaceholder":146},[112,27195,27196,27198,27200,27202,27204,27206,27208,27210,27212,27214,27217,27219,27221,27223,27226],{"class":114,"line":351},[112,27197,26612],{"class":129},[112,27199,26615],{"class":163},[112,27201,425],{"class":129},[112,27203,26620],{"class":163},[112,27205,213],{"class":129},[112,27207,133],{"class":163},[112,27209,425],{"class":129},[112,27211,26620],{"class":163},[112,27213,213],{"class":129},[112,27215,27216],{"class":163},"subtract",[112,27218,425],{"class":129},[112,27220,19956],{"class":156},[112,27222,447],{"class":129},[112,27224,27225],{"class":136},"'hour'",[112,27227,27184],{"class":129},[112,27229,27230],{"class":114,"line":361},[112,27231,27232],{"class":118},"\u002F\u002F 出力: 2時間前\n",[83,27234,27236],{"id":27235},"_4-customparseformat-カスタムフォーマットのパース","4. CustomParseFormat - カスタムフォーマットのパース",[103,27238,27240],{"className":26556,"code":27239,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\nimport customParseFormat from 'dayjs\u002Fplugin\u002FcustomParseFormat'\n\ndayjs.extend(customParseFormat)\n\nconst date = dayjs('2021\u002F11\u002F29', 'YYYY\u002FMM\u002FDD')\nconsole.log(date.format('YYYY-MM-DD'))\n\u002F\u002F 出力: 2021-11-29\n\n\u002F\u002F 厳密なパース（形式が一致しない場合はInvalid Date）\nconst strictDate = dayjs('2021-13-01', 'YYYY-MM-DD', true)\nconsole.log(strictDate.isValid())\n\u002F\u002F 出力: false (13月は存在しない)\n",[90,27241,27242,27252,27264,27268,27277,27281,27305,27322,27327,27331,27336,27362,27376],{"__ignoreMap":108},[112,27243,27244,27246,27248,27250],{"class":114,"line":115},[112,27245,126],{"class":125},[112,27247,26567],{"class":129},[112,27249,133],{"class":125},[112,27251,26572],{"class":136},[112,27253,27254,27256,27259,27261],{"class":114,"line":122},[112,27255,126],{"class":125},[112,27257,27258],{"class":129}," customParseFormat ",[112,27260,133],{"class":125},[112,27262,27263],{"class":136}," 'dayjs\u002Fplugin\u002FcustomParseFormat'\n",[112,27265,27266],{"class":114,"line":143},[112,27267,147],{"emptyLinePlaceholder":146},[112,27269,27270,27272,27274],{"class":114,"line":150},[112,27271,26593],{"class":129},[112,27273,26808],{"class":163},[112,27275,27276],{"class":129},"(customParseFormat)\n",[112,27278,27279],{"class":114,"line":170},[112,27280,147],{"emptyLinePlaceholder":146},[112,27282,27283,27285,27288,27290,27293,27295,27298,27300,27303],{"class":114,"line":182},[112,27284,153],{"class":125},[112,27286,27287],{"class":156}," date",[112,27289,160],{"class":125},[112,27291,27292],{"class":163}," dayjs",[112,27294,425],{"class":129},[112,27296,27297],{"class":136},"'2021\u002F11\u002F29'",[112,27299,447],{"class":129},[112,27301,27302],{"class":136},"'YYYY\u002FMM\u002FDD'",[112,27304,431],{"class":129},[112,27306,27307,27309,27311,27314,27316,27318,27320],{"class":114,"line":193},[112,27308,26612],{"class":129},[112,27310,26615],{"class":163},[112,27312,27313],{"class":129},"(date.",[112,27315,26625],{"class":163},[112,27317,425],{"class":129},[112,27319,26891],{"class":136},[112,27321,26633],{"class":129},[112,27323,27324],{"class":114,"line":205},[112,27325,27326],{"class":118},"\u002F\u002F 出力: 2021-11-29\n",[112,27328,27329],{"class":114,"line":241},[112,27330,147],{"emptyLinePlaceholder":146},[112,27332,27333],{"class":114,"line":247},[112,27334,27335],{"class":118},"\u002F\u002F 厳密なパース（形式が一致しない場合はInvalid Date）\n",[112,27337,27338,27340,27343,27345,27347,27349,27352,27354,27356,27358,27360],{"class":114,"line":351},[112,27339,153],{"class":125},[112,27341,27342],{"class":156}," strictDate",[112,27344,160],{"class":125},[112,27346,27292],{"class":163},[112,27348,425],{"class":129},[112,27350,27351],{"class":136},"'2021-13-01'",[112,27353,447],{"class":129},[112,27355,26891],{"class":136},[112,27357,447],{"class":129},[112,27359,4345],{"class":156},[112,27361,431],{"class":129},[112,27363,27364,27366,27368,27371,27374],{"class":114,"line":361},[112,27365,26612],{"class":129},[112,27367,26615],{"class":163},[112,27369,27370],{"class":129},"(strictDate.",[112,27372,27373],{"class":163},"isValid",[112,27375,17821],{"class":129},[112,27377,27378],{"class":114,"line":367},[112,27379,27380],{"class":118},"\u002F\u002F 出力: false (13月は存在しない)\n",[83,27382,27384],{"id":27383},"_5-issameorafter-issameorbefore-日付比較","5. IsSameOrAfter \u002F IsSameOrBefore - 日付比較",[103,27386,27388],{"className":26556,"code":27387,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\nimport isSameOrAfter from 'dayjs\u002Fplugin\u002FisSameOrAfter'\nimport isSameOrBefore from 'dayjs\u002Fplugin\u002FisSameOrBefore'\n\ndayjs.extend(isSameOrAfter)\ndayjs.extend(isSameOrBefore)\n\nconst start = dayjs('2021-11-01')\nconst end = dayjs('2021-11-30')\nconst target = dayjs('2021-11-15')\n\n\u002F\u002F 期間内かチェック\nif (target.isSameOrAfter(start) && target.isSameOrBefore(end)) {\n  console.log('期間内です')\n}\n",[90,27389,27390,27400,27412,27424,27428,27437,27446,27450,27467,27484,27501,27505,27510,27536,27550],{"__ignoreMap":108},[112,27391,27392,27394,27396,27398],{"class":114,"line":115},[112,27393,126],{"class":125},[112,27395,26567],{"class":129},[112,27397,133],{"class":125},[112,27399,26572],{"class":136},[112,27401,27402,27404,27407,27409],{"class":114,"line":122},[112,27403,126],{"class":125},[112,27405,27406],{"class":129}," isSameOrAfter ",[112,27408,133],{"class":125},[112,27410,27411],{"class":136}," 'dayjs\u002Fplugin\u002FisSameOrAfter'\n",[112,27413,27414,27416,27419,27421],{"class":114,"line":143},[112,27415,126],{"class":125},[112,27417,27418],{"class":129}," isSameOrBefore ",[112,27420,133],{"class":125},[112,27422,27423],{"class":136}," 'dayjs\u002Fplugin\u002FisSameOrBefore'\n",[112,27425,27426],{"class":114,"line":150},[112,27427,147],{"emptyLinePlaceholder":146},[112,27429,27430,27432,27434],{"class":114,"line":170},[112,27431,26593],{"class":129},[112,27433,26808],{"class":163},[112,27435,27436],{"class":129},"(isSameOrAfter)\n",[112,27438,27439,27441,27443],{"class":114,"line":182},[112,27440,26593],{"class":129},[112,27442,26808],{"class":163},[112,27444,27445],{"class":129},"(isSameOrBefore)\n",[112,27447,27448],{"class":114,"line":193},[112,27449,147],{"emptyLinePlaceholder":146},[112,27451,27452,27454,27457,27459,27461,27463,27465],{"class":114,"line":205},[112,27453,153],{"class":125},[112,27455,27456],{"class":156}," start",[112,27458,160],{"class":125},[112,27460,27292],{"class":163},[112,27462,425],{"class":129},[112,27464,26837],{"class":136},[112,27466,431],{"class":129},[112,27468,27469,27471,27474,27476,27478,27480,27482],{"class":114,"line":241},[112,27470,153],{"class":125},[112,27472,27473],{"class":156}," end",[112,27475,160],{"class":125},[112,27477,27292],{"class":163},[112,27479,425],{"class":129},[112,27481,26859],{"class":136},[112,27483,431],{"class":129},[112,27485,27486,27488,27491,27493,27495,27497,27499],{"class":114,"line":247},[112,27487,153],{"class":125},[112,27489,27490],{"class":156}," target",[112,27492,160],{"class":125},[112,27494,27292],{"class":163},[112,27496,425],{"class":129},[112,27498,26848],{"class":136},[112,27500,431],{"class":129},[112,27502,27503],{"class":114,"line":351},[112,27504,147],{"emptyLinePlaceholder":146},[112,27506,27507],{"class":114,"line":361},[112,27508,27509],{"class":118},"\u002F\u002F 期間内かチェック\n",[112,27511,27512,27515,27518,27521,27524,27527,27530,27533],{"class":114,"line":367},[112,27513,27514],{"class":125},"if",[112,27516,27517],{"class":129}," (target.",[112,27519,27520],{"class":163},"isSameOrAfter",[112,27522,27523],{"class":129},"(start) ",[112,27525,27526],{"class":125},"&&",[112,27528,27529],{"class":129}," target.",[112,27531,27532],{"class":163},"isSameOrBefore",[112,27534,27535],{"class":129},"(end)) {\n",[112,27537,27538,27541,27543,27545,27548],{"class":114,"line":373},[112,27539,27540],{"class":129},"  console.",[112,27542,26615],{"class":163},[112,27544,425],{"class":129},[112,27546,27547],{"class":136},"'期間内です'",[112,27549,431],{"class":129},[112,27551,27552],{"class":114,"line":379},[112,27553,1452],{"class":129},[11,27555,21999],{"id":21999},[15,27557,27558],{},"プロジェクト全体で使うDay.jsの設定をモジュール化します。",[103,27560,27562],{"className":26556,"code":27561,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\n\n\u002F\u002F ロケール\nimport 'dayjs\u002Flocale\u002Fja'\n\n\u002F\u002F プラグイン\nimport utc from 'dayjs\u002Fplugin\u002Futc'\nimport timezone from 'dayjs\u002Fplugin\u002Ftimezone'\nimport minMax from 'dayjs\u002Fplugin\u002FminMax'\nimport relativeTime from 'dayjs\u002Fplugin\u002FrelativeTime'\nimport isSameOrAfter from 'dayjs\u002Fplugin\u002FisSameOrAfter'\nimport isSameOrBefore from 'dayjs\u002Fplugin\u002FisSameOrBefore'\nimport customParseFormat from 'dayjs\u002Fplugin\u002FcustomParseFormat'\n\n\u002F\u002F プラグインを登録\ndayjs.extend(utc)\ndayjs.extend(timezone)\ndayjs.extend(minMax)\ndayjs.extend(relativeTime)\ndayjs.extend(isSameOrAfter)\ndayjs.extend(isSameOrBefore)\ndayjs.extend(customParseFormat)\n\n\u002F\u002F 日本語ロケールを設定\ndayjs.locale('ja')\n\n\u002F\u002F デフォルトタイムゾーンを日本時間に設定\ndayjs.tz.setDefault('Asia\u002FTokyo')\n\nexport default dayjs\n",[90,27563,27564,27574,27578,27583,27589,27593,27598,27608,27618,27628,27638,27648,27658,27668,27672,27677,27685,27693,27701,27709,27717,27725,27733,27737,27741,27753,27757,27762,27776,27780],{"__ignoreMap":108},[112,27565,27566,27568,27570,27572],{"class":114,"line":115},[112,27567,126],{"class":125},[112,27569,26567],{"class":129},[112,27571,133],{"class":125},[112,27573,26572],{"class":136},[112,27575,27576],{"class":114,"line":122},[112,27577,147],{"emptyLinePlaceholder":146},[112,27579,27580],{"class":114,"line":143},[112,27581,27582],{"class":118},"\u002F\u002F ロケール\n",[112,27584,27585,27587],{"class":114,"line":150},[112,27586,126],{"class":125},[112,27588,26579],{"class":136},[112,27590,27591],{"class":114,"line":170},[112,27592,147],{"emptyLinePlaceholder":146},[112,27594,27595],{"class":114,"line":182},[112,27596,27597],{"class":118},"\u002F\u002F プラグイン\n",[112,27599,27600,27602,27604,27606],{"class":114,"line":193},[112,27601,126],{"class":125},[112,27603,26954],{"class":129},[112,27605,133],{"class":125},[112,27607,26959],{"class":136},[112,27609,27610,27612,27614,27616],{"class":114,"line":205},[112,27611,126],{"class":125},[112,27613,26966],{"class":129},[112,27615,133],{"class":125},[112,27617,26971],{"class":136},[112,27619,27620,27622,27624,27626],{"class":114,"line":241},[112,27621,126],{"class":125},[112,27623,26792],{"class":129},[112,27625,133],{"class":125},[112,27627,26797],{"class":136},[112,27629,27630,27632,27634,27636],{"class":114,"line":247},[112,27631,126],{"class":125},[112,27633,27108],{"class":129},[112,27635,133],{"class":125},[112,27637,27113],{"class":136},[112,27639,27640,27642,27644,27646],{"class":114,"line":351},[112,27641,126],{"class":125},[112,27643,27406],{"class":129},[112,27645,133],{"class":125},[112,27647,27411],{"class":136},[112,27649,27650,27652,27654,27656],{"class":114,"line":361},[112,27651,126],{"class":125},[112,27653,27418],{"class":129},[112,27655,133],{"class":125},[112,27657,27423],{"class":136},[112,27659,27660,27662,27664,27666],{"class":114,"line":367},[112,27661,126],{"class":125},[112,27663,27258],{"class":129},[112,27665,133],{"class":125},[112,27667,27263],{"class":136},[112,27669,27670],{"class":114,"line":373},[112,27671,147],{"emptyLinePlaceholder":146},[112,27673,27674],{"class":114,"line":379},[112,27675,27676],{"class":118},"\u002F\u002F プラグインを登録\n",[112,27678,27679,27681,27683],{"class":114,"line":1302},[112,27680,26593],{"class":129},[112,27682,26808],{"class":163},[112,27684,26984],{"class":129},[112,27686,27687,27689,27691],{"class":114,"line":1502},[112,27688,26593],{"class":129},[112,27690,26808],{"class":163},[112,27692,26993],{"class":129},[112,27694,27695,27697,27699],{"class":114,"line":1507},[112,27696,26593],{"class":129},[112,27698,26808],{"class":163},[112,27700,26811],{"class":129},[112,27702,27703,27705,27707],{"class":114,"line":1512},[112,27704,26593],{"class":129},[112,27706,26808],{"class":163},[112,27708,27132],{"class":129},[112,27710,27711,27713,27715],{"class":114,"line":1518},[112,27712,26593],{"class":129},[112,27714,26808],{"class":163},[112,27716,27436],{"class":129},[112,27718,27719,27721,27723],{"class":114,"line":1524},[112,27720,26593],{"class":129},[112,27722,26808],{"class":163},[112,27724,27445],{"class":129},[112,27726,27727,27729,27731],{"class":114,"line":1530},[112,27728,26593],{"class":129},[112,27730,26808],{"class":163},[112,27732,27276],{"class":129},[112,27734,27735],{"class":114,"line":1536},[112,27736,147],{"emptyLinePlaceholder":146},[112,27738,27739],{"class":114,"line":1542},[112,27740,26588],{"class":118},[112,27742,27743,27745,27747,27749,27751],{"class":114,"line":2402},[112,27744,26593],{"class":129},[112,27746,26596],{"class":163},[112,27748,425],{"class":129},[112,27750,26601],{"class":136},[112,27752,431],{"class":129},[112,27754,27755],{"class":114,"line":2407},[112,27756,147],{"emptyLinePlaceholder":146},[112,27758,27759],{"class":114,"line":2413},[112,27760,27761],{"class":118},"\u002F\u002F デフォルトタイムゾーンを日本時間に設定\n",[112,27763,27764,27767,27770,27772,27774],{"class":114,"line":2446},[112,27765,27766],{"class":129},"dayjs.tz.",[112,27768,27769],{"class":163},"setDefault",[112,27771,425],{"class":129},[112,27773,27027],{"class":136},[112,27775,431],{"class":129},[112,27777,27778],{"class":114,"line":2451},[112,27779,147],{"emptyLinePlaceholder":146},[112,27781,27782,27784,27786],{"class":114,"line":2464},[112,27783,288],{"class":125},[112,27785,291],{"class":125},[112,27787,27788],{"class":129}," dayjs\n",[11,27790,27791],{"id":27791},"よくあるユースケース",[83,27793,27794],{"id":27794},"営業日の計算",[103,27796,27798],{"className":26556,"code":27797,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\nimport isoWeek from 'dayjs\u002Fplugin\u002FisoWeek'\n\ndayjs.extend(isoWeek)\n\nfunction isBusinessDay(date: dayjs.Dayjs): boolean {\n  const dayOfWeek = date.isoWeekday()\n  return dayOfWeek >= 1 && dayOfWeek \u003C= 5 \u002F\u002F 月曜(1)〜金曜(5)\n}\n\nconst today = dayjs()\nconsole.log(isBusinessDay(today) ? '営業日' : '休日')\n",[90,27799,27800,27810,27822,27826,27835,27839,27869,27886,27913,27917,27921,27934],{"__ignoreMap":108},[112,27801,27802,27804,27806,27808],{"class":114,"line":115},[112,27803,126],{"class":125},[112,27805,26567],{"class":129},[112,27807,133],{"class":125},[112,27809,26572],{"class":136},[112,27811,27812,27814,27817,27819],{"class":114,"line":122},[112,27813,126],{"class":125},[112,27815,27816],{"class":129}," isoWeek ",[112,27818,133],{"class":125},[112,27820,27821],{"class":136}," 'dayjs\u002Fplugin\u002FisoWeek'\n",[112,27823,27824],{"class":114,"line":143},[112,27825,147],{"emptyLinePlaceholder":146},[112,27827,27828,27830,27832],{"class":114,"line":150},[112,27829,26593],{"class":129},[112,27831,26808],{"class":163},[112,27833,27834],{"class":129},"(isoWeek)\n",[112,27836,27837],{"class":114,"line":170},[112,27838,147],{"emptyLinePlaceholder":146},[112,27840,27841,27843,27846,27848,27851,27853,27855,27857,27860,27862,27864,27867],{"class":114,"line":182},[112,27842,4159],{"class":125},[112,27844,27845],{"class":163}," isBusinessDay",[112,27847,425],{"class":129},[112,27849,27850],{"class":222},"date",[112,27852,2243],{"class":125},[112,27854,27292],{"class":163},[112,27856,2124],{"class":129},[112,27858,27859],{"class":163},"Dayjs",[112,27861,10186],{"class":129},[112,27863,2243],{"class":125},[112,27865,27866],{"class":156}," boolean",[112,27868,1294],{"class":129},[112,27870,27871,27873,27876,27878,27881,27884],{"class":114,"line":193},[112,27872,1974],{"class":125},[112,27874,27875],{"class":156}," dayOfWeek",[112,27877,160],{"class":125},[112,27879,27880],{"class":129}," date.",[112,27882,27883],{"class":163},"isoWeekday",[112,27885,630],{"class":129},[112,27887,27888,27890,27893,27896,27899,27902,27904,27907,27910],{"class":114,"line":205},[112,27889,2048],{"class":125},[112,27891,27892],{"class":129}," dayOfWeek ",[112,27894,27895],{"class":125},">=",[112,27897,27898],{"class":156}," 1",[112,27900,27901],{"class":125}," &&",[112,27903,27892],{"class":129},[112,27905,27906],{"class":125},"\u003C=",[112,27908,27909],{"class":156}," 5",[112,27911,27912],{"class":118}," \u002F\u002F 月曜(1)〜金曜(5)\n",[112,27914,27915],{"class":114,"line":241},[112,27916,1452],{"class":129},[112,27918,27919],{"class":114,"line":247},[112,27920,147],{"emptyLinePlaceholder":146},[112,27922,27923,27925,27928,27930,27932],{"class":114,"line":351},[112,27924,153],{"class":125},[112,27926,27927],{"class":156}," today",[112,27929,160],{"class":125},[112,27931,27292],{"class":163},[112,27933,630],{"class":129},[112,27935,27936,27938,27940,27942,27945,27948,27950,27953,27956,27959],{"class":114,"line":361},[112,27937,26612],{"class":129},[112,27939,26615],{"class":163},[112,27941,425],{"class":129},[112,27943,27944],{"class":163},"isBusinessDay",[112,27946,27947],{"class":129},"(today) ",[112,27949,23460],{"class":125},[112,27951,27952],{"class":136}," '営業日'",[112,27954,27955],{"class":125}," :",[112,27957,27958],{"class":136}," '休日'",[112,27960,431],{"class":129},[83,27962,27963],{"id":27963},"年齢計算",[103,27965,27967],{"className":26556,"code":27966,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\n\nfunction calculateAge(birthDate: string): number {\n  const birth = dayjs(birthDate)\n  const today = dayjs()\n  return today.diff(birth, 'year')\n}\n\nconsole.log(calculateAge('1990-01-01'))\n\u002F\u002F 出力: 31 (2021年時点)\n",[90,27968,27969,27979,27983,28007,28021,28033,28051,28055,28059,28077],{"__ignoreMap":108},[112,27970,27971,27973,27975,27977],{"class":114,"line":115},[112,27972,126],{"class":125},[112,27974,26567],{"class":129},[112,27976,133],{"class":125},[112,27978,26572],{"class":136},[112,27980,27981],{"class":114,"line":122},[112,27982,147],{"emptyLinePlaceholder":146},[112,27984,27985,27987,27990,27992,27995,27997,27999,28001,28003,28005],{"class":114,"line":143},[112,27986,4159],{"class":125},[112,27988,27989],{"class":163}," calculateAge",[112,27991,425],{"class":129},[112,27993,27994],{"class":222},"birthDate",[112,27996,2243],{"class":125},[112,27998,10478],{"class":156},[112,28000,10186],{"class":129},[112,28002,2243],{"class":125},[112,28004,17353],{"class":156},[112,28006,1294],{"class":129},[112,28008,28009,28011,28014,28016,28018],{"class":114,"line":150},[112,28010,1974],{"class":125},[112,28012,28013],{"class":156}," birth",[112,28015,160],{"class":125},[112,28017,27292],{"class":163},[112,28019,28020],{"class":129},"(birthDate)\n",[112,28022,28023,28025,28027,28029,28031],{"class":114,"line":170},[112,28024,1974],{"class":125},[112,28026,27927],{"class":156},[112,28028,160],{"class":125},[112,28030,27292],{"class":163},[112,28032,630],{"class":129},[112,28034,28035,28037,28040,28043,28046,28049],{"class":114,"line":182},[112,28036,2048],{"class":125},[112,28038,28039],{"class":129}," today.",[112,28041,28042],{"class":163},"diff",[112,28044,28045],{"class":129},"(birth, ",[112,28047,28048],{"class":136},"'year'",[112,28050,431],{"class":129},[112,28052,28053],{"class":114,"line":193},[112,28054,1452],{"class":129},[112,28056,28057],{"class":114,"line":205},[112,28058,147],{"emptyLinePlaceholder":146},[112,28060,28061,28063,28065,28067,28070,28072,28075],{"class":114,"line":241},[112,28062,26612],{"class":129},[112,28064,26615],{"class":163},[112,28066,425],{"class":129},[112,28068,28069],{"class":163},"calculateAge",[112,28071,425],{"class":129},[112,28073,28074],{"class":136},"'1990-01-01'",[112,28076,26633],{"class":129},[112,28078,28079],{"class":114,"line":247},[112,28080,28081],{"class":118},"\u002F\u002F 出力: 31 (2021年時点)\n",[83,28083,28084],{"id":28084},"期間の重複チェック",[103,28086,28088],{"className":26556,"code":28087,"language":26558,"meta":108,"style":108},"import dayjs from 'dayjs'\nimport isSameOrAfter from 'dayjs\u002Fplugin\u002FisSameOrAfter'\nimport isSameOrBefore from 'dayjs\u002Fplugin\u002FisSameOrBefore'\n\ndayjs.extend(isSameOrAfter)\ndayjs.extend(isSameOrBefore)\n\nfunction isOverlapping(\n  start1: dayjs.Dayjs,\n  end1: dayjs.Dayjs,\n  start2: dayjs.Dayjs,\n  end2: dayjs.Dayjs\n): boolean {\n  return start1.isSameOrBefore(end2) && end1.isSameOrAfter(start2)\n}\n\nconst period1Start = dayjs('2021-11-01')\nconst period1End = dayjs('2021-11-15')\nconst period2Start = dayjs('2021-11-10')\nconst period2End = dayjs('2021-11-20')\n\nconsole.log(isOverlapping(period1Start, period1End, period2Start, period2End))\n\u002F\u002F 出力: true (重複している)\n",[90,28089,28090,28100,28110,28120,28124,28132,28140,28144,28153,28168,28183,28198,28212,28222,28244,28248,28252,28269,28286,28304,28322,28326,28340],{"__ignoreMap":108},[112,28091,28092,28094,28096,28098],{"class":114,"line":115},[112,28093,126],{"class":125},[112,28095,26567],{"class":129},[112,28097,133],{"class":125},[112,28099,26572],{"class":136},[112,28101,28102,28104,28106,28108],{"class":114,"line":122},[112,28103,126],{"class":125},[112,28105,27406],{"class":129},[112,28107,133],{"class":125},[112,28109,27411],{"class":136},[112,28111,28112,28114,28116,28118],{"class":114,"line":143},[112,28113,126],{"class":125},[112,28115,27418],{"class":129},[112,28117,133],{"class":125},[112,28119,27423],{"class":136},[112,28121,28122],{"class":114,"line":150},[112,28123,147],{"emptyLinePlaceholder":146},[112,28125,28126,28128,28130],{"class":114,"line":170},[112,28127,26593],{"class":129},[112,28129,26808],{"class":163},[112,28131,27436],{"class":129},[112,28133,28134,28136,28138],{"class":114,"line":182},[112,28135,26593],{"class":129},[112,28137,26808],{"class":163},[112,28139,27445],{"class":129},[112,28141,28142],{"class":114,"line":193},[112,28143,147],{"emptyLinePlaceholder":146},[112,28145,28146,28148,28151],{"class":114,"line":205},[112,28147,4159],{"class":125},[112,28149,28150],{"class":163}," isOverlapping",[112,28152,2304],{"class":129},[112,28154,28155,28158,28160,28162,28164,28166],{"class":114,"line":241},[112,28156,28157],{"class":222},"  start1",[112,28159,2243],{"class":125},[112,28161,27292],{"class":163},[112,28163,2124],{"class":129},[112,28165,27859],{"class":163},[112,28167,179],{"class":129},[112,28169,28170,28173,28175,28177,28179,28181],{"class":114,"line":247},[112,28171,28172],{"class":222},"  end1",[112,28174,2243],{"class":125},[112,28176,27292],{"class":163},[112,28178,2124],{"class":129},[112,28180,27859],{"class":163},[112,28182,179],{"class":129},[112,28184,28185,28188,28190,28192,28194,28196],{"class":114,"line":351},[112,28186,28187],{"class":222},"  start2",[112,28189,2243],{"class":125},[112,28191,27292],{"class":163},[112,28193,2124],{"class":129},[112,28195,27859],{"class":163},[112,28197,179],{"class":129},[112,28199,28200,28203,28205,28207,28209],{"class":114,"line":361},[112,28201,28202],{"class":222},"  end2",[112,28204,2243],{"class":125},[112,28206,27292],{"class":163},[112,28208,2124],{"class":129},[112,28210,28211],{"class":163},"Dayjs\n",[112,28213,28214,28216,28218,28220],{"class":114,"line":367},[112,28215,10186],{"class":129},[112,28217,2243],{"class":125},[112,28219,27866],{"class":156},[112,28221,1294],{"class":129},[112,28223,28224,28226,28229,28231,28234,28236,28239,28241],{"class":114,"line":373},[112,28225,2048],{"class":125},[112,28227,28228],{"class":129}," start1.",[112,28230,27532],{"class":163},[112,28232,28233],{"class":129},"(end2) ",[112,28235,27526],{"class":125},[112,28237,28238],{"class":129}," end1.",[112,28240,27520],{"class":163},[112,28242,28243],{"class":129},"(start2)\n",[112,28245,28246],{"class":114,"line":379},[112,28247,1452],{"class":129},[112,28249,28250],{"class":114,"line":1302},[112,28251,147],{"emptyLinePlaceholder":146},[112,28253,28254,28256,28259,28261,28263,28265,28267],{"class":114,"line":1502},[112,28255,153],{"class":125},[112,28257,28258],{"class":156}," period1Start",[112,28260,160],{"class":125},[112,28262,27292],{"class":163},[112,28264,425],{"class":129},[112,28266,26837],{"class":136},[112,28268,431],{"class":129},[112,28270,28271,28273,28276,28278,28280,28282,28284],{"class":114,"line":1507},[112,28272,153],{"class":125},[112,28274,28275],{"class":156}," period1End",[112,28277,160],{"class":125},[112,28279,27292],{"class":163},[112,28281,425],{"class":129},[112,28283,26848],{"class":136},[112,28285,431],{"class":129},[112,28287,28288,28290,28293,28295,28297,28299,28302],{"class":114,"line":1512},[112,28289,153],{"class":125},[112,28291,28292],{"class":156}," period2Start",[112,28294,160],{"class":125},[112,28296,27292],{"class":163},[112,28298,425],{"class":129},[112,28300,28301],{"class":136},"'2021-11-10'",[112,28303,431],{"class":129},[112,28305,28306,28308,28311,28313,28315,28317,28320],{"class":114,"line":1518},[112,28307,153],{"class":125},[112,28309,28310],{"class":156}," period2End",[112,28312,160],{"class":125},[112,28314,27292],{"class":163},[112,28316,425],{"class":129},[112,28318,28319],{"class":136},"'2021-11-20'",[112,28321,431],{"class":129},[112,28323,28324],{"class":114,"line":1524},[112,28325,147],{"emptyLinePlaceholder":146},[112,28327,28328,28330,28332,28334,28337],{"class":114,"line":1530},[112,28329,26612],{"class":129},[112,28331,26615],{"class":163},[112,28333,425],{"class":129},[112,28335,28336],{"class":163},"isOverlapping",[112,28338,28339],{"class":129},"(period1Start, period1End, period2Start, period2End))\n",[112,28341,28342],{"class":114,"line":1536},[112,28343,28344],{"class":118},"\u002F\u002F 出力: true (重複している)\n",[11,28346,28348],{"id":28347},"typescriptでの型定義","TypeScriptでの型定義",[103,28350,28352],{"className":26556,"code":28351,"language":26558,"meta":108,"style":108},"import dayjs, { Dayjs } from 'dayjs'\n\ninterface DateRange {\n  start: Dayjs\n  end: Dayjs\n}\n\nfunction formatDateRange(range: DateRange): string {\n  return `${range.start.format('YYYY\u002FMM\u002FDD')} 〜 ${range.end.format('YYYY\u002FMM\u002FDD')}`\n}\n\nconst range: DateRange = {\n  start: dayjs('2021-11-01'),\n  end: dayjs('2021-11-30'),\n}\n\nconsole.log(formatDateRange(range))\n\u002F\u002F 出力: 2021\u002F11\u002F01 〜 2021\u002F11\u002F30\n",[90,28353,28354,28365,28369,28379,28389,28398,28402,28406,28430,28476,28480,28484,28499,28512,28525,28529,28533,28547],{"__ignoreMap":108},[112,28355,28356,28358,28361,28363],{"class":114,"line":115},[112,28357,126],{"class":125},[112,28359,28360],{"class":129}," dayjs, { Dayjs } ",[112,28362,133],{"class":125},[112,28364,26572],{"class":136},[112,28366,28367],{"class":114,"line":122},[112,28368,147],{"emptyLinePlaceholder":146},[112,28370,28371,28374,28377],{"class":114,"line":143},[112,28372,28373],{"class":125},"interface",[112,28375,28376],{"class":163}," DateRange",[112,28378,1294],{"class":129},[112,28380,28381,28384,28386],{"class":114,"line":150},[112,28382,28383],{"class":222},"  start",[112,28385,2243],{"class":125},[112,28387,28388],{"class":163}," Dayjs\n",[112,28390,28391,28394,28396],{"class":114,"line":170},[112,28392,28393],{"class":222},"  end",[112,28395,2243],{"class":125},[112,28397,28388],{"class":163},[112,28399,28400],{"class":114,"line":182},[112,28401,1452],{"class":129},[112,28403,28404],{"class":114,"line":193},[112,28405,147],{"emptyLinePlaceholder":146},[112,28407,28408,28410,28413,28415,28418,28420,28422,28424,28426,28428],{"class":114,"line":205},[112,28409,4159],{"class":125},[112,28411,28412],{"class":163}," formatDateRange",[112,28414,425],{"class":129},[112,28416,28417],{"class":222},"range",[112,28419,2243],{"class":125},[112,28421,28376],{"class":163},[112,28423,10186],{"class":129},[112,28425,2243],{"class":125},[112,28427,10478],{"class":156},[112,28429,1294],{"class":129},[112,28431,28432,28434,28437,28439,28441,28444,28446,28448,28450,28452,28454,28457,28459,28461,28463,28465,28467,28469,28471,28473],{"class":114,"line":241},[112,28433,2048],{"class":125},[112,28435,28436],{"class":136}," `${",[112,28438,28417],{"class":129},[112,28440,2124],{"class":136},[112,28442,28443],{"class":129},"start",[112,28445,2124],{"class":136},[112,28447,26625],{"class":163},[112,28449,425],{"class":136},[112,28451,27302],{"class":136},[112,28453,10186],{"class":136},[112,28455,28456],{"class":136},"} 〜 ${",[112,28458,28417],{"class":129},[112,28460,2124],{"class":136},[112,28462,20891],{"class":129},[112,28464,2124],{"class":136},[112,28466,26625],{"class":163},[112,28468,425],{"class":136},[112,28470,27302],{"class":136},[112,28472,10186],{"class":136},[112,28474,28475],{"class":136},"}`\n",[112,28477,28478],{"class":114,"line":247},[112,28479,1452],{"class":129},[112,28481,28482],{"class":114,"line":351},[112,28483,147],{"emptyLinePlaceholder":146},[112,28485,28486,28488,28491,28493,28495,28497],{"class":114,"line":361},[112,28487,153],{"class":125},[112,28489,28490],{"class":156}," range",[112,28492,2243],{"class":125},[112,28494,28376],{"class":163},[112,28496,160],{"class":125},[112,28498,1294],{"class":129},[112,28500,28501,28504,28506,28508,28510],{"class":114,"line":367},[112,28502,28503],{"class":129},"  start: ",[112,28505,26620],{"class":163},[112,28507,425],{"class":129},[112,28509,26837],{"class":136},[112,28511,15842],{"class":129},[112,28513,28514,28517,28519,28521,28523],{"class":114,"line":373},[112,28515,28516],{"class":129},"  end: ",[112,28518,26620],{"class":163},[112,28520,425],{"class":129},[112,28522,26859],{"class":136},[112,28524,15842],{"class":129},[112,28526,28527],{"class":114,"line":379},[112,28528,1452],{"class":129},[112,28530,28531],{"class":114,"line":1302},[112,28532,147],{"emptyLinePlaceholder":146},[112,28534,28535,28537,28539,28541,28544],{"class":114,"line":1502},[112,28536,26612],{"class":129},[112,28538,26615],{"class":163},[112,28540,425],{"class":129},[112,28542,28543],{"class":163},"formatDateRange",[112,28545,28546],{"class":129},"(range))\n",[112,28548,28549],{"class":114,"line":1507},[112,28550,28551],{"class":118},"\u002F\u002F 出力: 2021\u002F11\u002F01 〜 2021\u002F11\u002F30\n",[11,28553,919],{"id":919},[15,28555,28556],{},"Day.jsを効果的に使うためのポイントは次の通りです。",[31,28558,28559,28568,28574,28580],{},[34,28560,28561,28563,28564,28567],{},[27,28562,26547],{}," 日本語表示にするために",[90,28565,28566],{},"dayjs.locale('ja')","を設定",[34,28569,28570,28573],{},[27,28571,28572],{},"プラグイン"," 必要な機能だけをインポートして軽量に保つ",[34,28575,28576,28579],{},[27,28577,28578],{},"タイムゾーン"," UTC\u002FTimezoneプラグインで正確な時刻を扱う",[34,28581,28582,28585],{},[27,28583,28584],{},"型安全性"," TypeScriptと組み合わせて型安全な日付処理を実現",[15,28587,28588],{},"軽量かつ強力なDay.jsで、日付・時刻の処理を効率化できます。",[83,28590,2930],{"id":2930},[31,28592,28593,28599,28606],{},[34,28594,28595],{},[759,28596,28598],{"href":26511,"rel":28597},[763],"Day.js 公式ドキュメント",[34,28600,28601],{},[759,28602,28605],{"href":28603,"rel":28604},"https:\u002F\u002Fday.js.org\u002Fdocs\u002Fen\u002Fplugin\u002Fplugin",[763],"プラグイン一覧",[34,28607,28608],{},[759,28609,28612],{"href":28610,"rel":28611},"https:\u002F\u002Fgithub.com\u002Fiamkun\u002Fdayjs\u002Ftree\u002Fdev\u002Fsrc\u002Flocale",[763],"ロケール一覧",[944,28614,28615],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":108,"searchDepth":122,"depth":122,"links":28617},[28618,28619,28623,28630,28631,28636,28637],{"id":26505,"depth":122,"text":26506},{"id":26547,"depth":122,"text":26547,"children":28620},[28621,28622],{"id":26553,"depth":143,"text":26553},{"id":26641,"depth":143,"text":26642},{"id":26763,"depth":122,"text":26763,"children":28624},[28625,28626,28627,28628,28629],{"id":26769,"depth":143,"text":26770},{"id":26931,"depth":143,"text":26932},{"id":27085,"depth":143,"text":27086},{"id":27235,"depth":143,"text":27236},{"id":27383,"depth":143,"text":27384},{"id":21999,"depth":122,"text":21999},{"id":27791,"depth":122,"text":27791,"children":28632},[28633,28634,28635],{"id":27794,"depth":143,"text":27794},{"id":27963,"depth":143,"text":27963},{"id":28084,"depth":143,"text":28084},{"id":28347,"depth":122,"text":28348},{"id":919,"depth":122,"text":919,"children":28638},[28639],{"id":2930,"depth":143,"text":2930},"2021-11-29","Day.jsで日本語ロケールの設定方法と、よく使うプラグインの導入・活用方法について解説します。",{"tags":28643},[26620,1911,28644],"datetime","\u002Fblog\u002Fdayjs-japanese-locale",{"title":26500,"description":28641},"blog\u002Fdayjs-japanese-locale","hSkUoG99_Q54eCADiudr1I_ABN0LFJX1TNrfzjVXxo4",{"id":28650,"title":28651,"body":28652,"date":29613,"description":29614,"extension":962,"meta":29615,"navigation":146,"path":29618,"seo":29619,"stem":29620,"__hash__":29621},"blog\u002Fblog\u002Fdocker-compose-github-actions-cicd.md","docker-compose + GitHub ActionsでE2Eテストを自動化する",{"type":8,"value":28653,"toc":29598},[28654,28656,28663,28667,28671,28717,28721,28775,28777,28780,29110,29114,29119,29323,29333,29336,29339,29384,29387,29393,29397,29400,29564,29566,29569,29589,29595],[11,28655,13],{"id":13},[15,28657,28658,28662],{},[759,28659,28661],{"href":28660},"\u002Fblog\u002Fjest-nestjs-e2e-testing","前回の記事","で作成したE2Eテストを、docker-composeで実行できるようにし、GitHub Actionsで自動化します。",[11,28664,28666],{"id":28665},"dockerfileの作成","Dockerfileの作成",[83,28668,28670],{"id":28669},"e2eテスト実行用dockerfile","E2Eテスト実行用Dockerfile",[103,28672,28674],{"className":18293,"code":28673,"language":18295,"meta":108,"style":108},"# Dockerfile.test\nFROM node:14.16.1-alpine as build-stage\n\nWORKDIR \u002Fwork\n\nCOPY . \u002Fwork\u002F\n\nRUN npm install\n\nCMD [\"npm\",\"run\",\"test:e2e\"]\n",[90,28675,28676,28681,28685,28689,28693,28697,28701,28705,28709,28713],{"__ignoreMap":108},[112,28677,28678],{"class":114,"line":115},[112,28679,28680],{},"# Dockerfile.test\n",[112,28682,28683],{"class":114,"line":122},[112,28684,18307],{},[112,28686,28687],{"class":114,"line":143},[112,28688,147],{"emptyLinePlaceholder":146},[112,28690,28691],{"class":114,"line":150},[112,28692,18316],{},[112,28694,28695],{"class":114,"line":170},[112,28696,147],{"emptyLinePlaceholder":146},[112,28698,28699],{"class":114,"line":182},[112,28700,18325],{},[112,28702,28703],{"class":114,"line":193},[112,28704,147],{"emptyLinePlaceholder":146},[112,28706,28707],{"class":114,"line":205},[112,28708,18334],{},[112,28710,28711],{"class":114,"line":241},[112,28712,147],{"emptyLinePlaceholder":146},[112,28714,28715],{"class":114,"line":247},[112,28716,18343],{},[83,28718,28720],{"id":28719},"マイグレーション実行用dockerfile","マイグレーション実行用Dockerfile",[103,28722,28724],{"className":18293,"code":28723,"language":18295,"meta":108,"style":108},"# Dockerfile.migrate\nFROM node:14.16.1-alpine\n\nWORKDIR \u002Fwork\n\nCOPY .\u002Fsrc\u002Fdatabases \u002Fwork\u002Fsrc\u002Fdatabases\nCOPY .\u002Fpackage.json .\u002Fpackage-lock.json .\u002Formconfig.ts .\u002Ftsconfig.json \u002Fwork\u002F\n\nRUN npm install\n\nCMD [\"npm\", \"run\", \"typeorm\", \"migration:run\"]\n",[90,28725,28726,28731,28736,28740,28744,28748,28753,28758,28762,28766,28770],{"__ignoreMap":108},[112,28727,28728],{"class":114,"line":115},[112,28729,28730],{},"# Dockerfile.migrate\n",[112,28732,28733],{"class":114,"line":122},[112,28734,28735],{},"FROM node:14.16.1-alpine\n",[112,28737,28738],{"class":114,"line":143},[112,28739,147],{"emptyLinePlaceholder":146},[112,28741,28742],{"class":114,"line":150},[112,28743,18316],{},[112,28745,28746],{"class":114,"line":170},[112,28747,147],{"emptyLinePlaceholder":146},[112,28749,28750],{"class":114,"line":182},[112,28751,28752],{},"COPY .\u002Fsrc\u002Fdatabases \u002Fwork\u002Fsrc\u002Fdatabases\n",[112,28754,28755],{"class":114,"line":193},[112,28756,28757],{},"COPY .\u002Fpackage.json .\u002Fpackage-lock.json .\u002Formconfig.ts .\u002Ftsconfig.json \u002Fwork\u002F\n",[112,28759,28760],{"class":114,"line":205},[112,28761,147],{"emptyLinePlaceholder":146},[112,28763,28764],{"class":114,"line":241},[112,28765,18334],{},[112,28767,28768],{"class":114,"line":247},[112,28769,147],{"emptyLinePlaceholder":146},[112,28771,28772],{"class":114,"line":351},[112,28773,28774],{},"CMD [\"npm\", \"run\", \"typeorm\", \"migration:run\"]\n",[11,28776,12891],{"id":12891},[15,28778,28779],{},"E2Eテスト用のdocker-compose設定ファイルを作成します。",[103,28781,28783],{"className":9132,"code":28782,"language":9134,"meta":108,"style":108},"# unit-test.yml\nversion: '3'\n\nservices:\n  app:\n    build:\n      context: \".\"\n      dockerfile: \"Dockerfile.test\"\n    image: app-test\n    container_name: app-test\n    ports:\n      - '3000:3000'\n    environment:\n      PORT: 3000\n      TZ: 'Asia\u002FTokyo'\n      DB_HOST: 'db'\n      DB_PORT: '3306'\n      DB_USERNAME: 'test_user'\n      DB_PASSWORD: 'test_password'\n      DB_NAME: 'test_db'\n      REDIS_HOST: 'redis'\n      REDIS_PORT: '6379'\n    depends_on:\n      - db\n      - redis\n\n  redis:\n    image: redis:5.0\n    container_name: redis_container-test\n    ports:\n      - \"6379:6379\"\n\n  db:\n    image: mysql:8.0\n    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci\n    container_name: db_container_test\n    ports:\n      - 3306:3306\n    environment:\n      TZ: 'Asia\u002FTokyo'\n      MYSQL_ROOT_PASSWORD: root_password\n      MYSQL_DATABASE: test_db\n      MYSQL_USER: test_user\n      MYSQL_PASSWORD: test_password\n",[90,28784,28785,28789,28797,28801,28807,28813,28819,28827,28836,28845,28853,28859,28865,28871,28879,28887,28896,28904,28913,28922,28931,28941,28951,28957,28964,28971,28975,28982,28991,29000,29006,29013,29017,29023,29031,29039,29048,29054,29060,29066,29074,29083,29092,29101],{"__ignoreMap":108},[112,28786,28787],{"class":114,"line":115},[112,28788,18356],{"class":118},[112,28790,28791,28793,28795],{"class":114,"line":122},[112,28792,9142],{"class":9141},[112,28794,567],{"class":129},[112,28796,15224],{"class":136},[112,28798,28799],{"class":114,"line":143},[112,28800,147],{"emptyLinePlaceholder":146},[112,28802,28803,28805],{"class":114,"line":150},[112,28804,9156],{"class":9141},[112,28806,5004],{"class":129},[112,28808,28809,28811],{"class":114,"line":170},[112,28810,18379],{"class":9141},[112,28812,5004],{"class":129},[112,28814,28815,28817],{"class":114,"line":182},[112,28816,18386],{"class":9141},[112,28818,5004],{"class":129},[112,28820,28821,28823,28825],{"class":114,"line":193},[112,28822,18393],{"class":9141},[112,28824,567],{"class":129},[112,28826,18398],{"class":136},[112,28828,28829,28831,28833],{"class":114,"line":205},[112,28830,18403],{"class":9141},[112,28832,567],{"class":129},[112,28834,28835],{"class":136},"\"Dockerfile.test\"\n",[112,28837,28838,28840,28842],{"class":114,"line":241},[112,28839,9170],{"class":9141},[112,28841,567],{"class":129},[112,28843,28844],{"class":136},"app-test\n",[112,28846,28847,28849,28851],{"class":114,"line":247},[112,28848,15264],{"class":9141},[112,28850,567],{"class":129},[112,28852,28844],{"class":136},[112,28854,28855,28857],{"class":114,"line":351},[112,28856,12979],{"class":9141},[112,28858,5004],{"class":129},[112,28860,28861,28863],{"class":114,"line":361},[112,28862,9204],{"class":129},[112,28864,18430],{"class":136},[112,28866,28867,28869],{"class":114,"line":367},[112,28868,9180],{"class":9141},[112,28870,5004],{"class":129},[112,28872,28873,28875,28877],{"class":114,"line":373},[112,28874,18441],{"class":9141},[112,28876,567],{"class":129},[112,28878,18446],{"class":156},[112,28880,28881,28883,28885],{"class":114,"line":379},[112,28882,15306],{"class":9141},[112,28884,567],{"class":129},[112,28886,15311],{"class":136},[112,28888,28889,28891,28893],{"class":114,"line":1302},[112,28890,18459],{"class":9141},[112,28892,567],{"class":129},[112,28894,28895],{"class":136},"'db'\n",[112,28897,28898,28900,28902],{"class":114,"line":1502},[112,28899,18469],{"class":9141},[112,28901,567],{"class":129},[112,28903,18474],{"class":136},[112,28905,28906,28908,28910],{"class":114,"line":1507},[112,28907,18479],{"class":9141},[112,28909,567],{"class":129},[112,28911,28912],{"class":136},"'test_user'\n",[112,28914,28915,28917,28919],{"class":114,"line":1512},[112,28916,18489],{"class":9141},[112,28918,567],{"class":129},[112,28920,28921],{"class":136},"'test_password'\n",[112,28923,28924,28926,28928],{"class":114,"line":1518},[112,28925,18499],{"class":9141},[112,28927,567],{"class":129},[112,28929,28930],{"class":136},"'test_db'\n",[112,28932,28933,28936,28938],{"class":114,"line":1524},[112,28934,28935],{"class":9141},"      REDIS_HOST",[112,28937,567],{"class":129},[112,28939,28940],{"class":136},"'redis'\n",[112,28942,28943,28946,28948],{"class":114,"line":1530},[112,28944,28945],{"class":9141},"      REDIS_PORT",[112,28947,567],{"class":129},[112,28949,28950],{"class":136},"'6379'\n",[112,28952,28953,28955],{"class":114,"line":1536},[112,28954,12993],{"class":9141},[112,28956,5004],{"class":129},[112,28958,28959,28961],{"class":114,"line":1542},[112,28960,9204],{"class":129},[112,28962,28963],{"class":136},"db\n",[112,28965,28966,28968],{"class":114,"line":2402},[112,28967,9204],{"class":129},[112,28969,28970],{"class":136},"redis\n",[112,28972,28973],{"class":114,"line":2407},[112,28974,147],{"emptyLinePlaceholder":146},[112,28976,28977,28980],{"class":114,"line":2413},[112,28978,28979],{"class":9141},"  redis",[112,28981,5004],{"class":129},[112,28983,28984,28986,28988],{"class":114,"line":2446},[112,28985,9170],{"class":9141},[112,28987,567],{"class":129},[112,28989,28990],{"class":136},"redis:5.0\n",[112,28992,28993,28995,28997],{"class":114,"line":2451},[112,28994,15264],{"class":9141},[112,28996,567],{"class":129},[112,28998,28999],{"class":136},"redis_container-test\n",[112,29001,29002,29004],{"class":114,"line":2464},[112,29003,12979],{"class":9141},[112,29005,5004],{"class":129},[112,29007,29008,29010],{"class":114,"line":2481},[112,29009,9204],{"class":129},[112,29011,29012],{"class":136},"\"6379:6379\"\n",[112,29014,29015],{"class":114,"line":2486},[112,29016,147],{"emptyLinePlaceholder":146},[112,29018,29019,29021],{"class":114,"line":3229},[112,29020,15239],{"class":9141},[112,29022,5004],{"class":129},[112,29024,29025,29027,29029],{"class":114,"line":3235},[112,29026,9170],{"class":9141},[112,29028,567],{"class":129},[112,29030,15250],{"class":136},[112,29032,29033,29035,29037],{"class":114,"line":3657},[112,29034,9219],{"class":9141},[112,29036,567],{"class":129},[112,29038,15259],{"class":136},[112,29040,29041,29043,29045],{"class":114,"line":3662},[112,29042,15264],{"class":9141},[112,29044,567],{"class":129},[112,29046,29047],{"class":136},"db_container_test\n",[112,29049,29050,29052],{"class":114,"line":3667},[112,29051,12979],{"class":9141},[112,29053,5004],{"class":129},[112,29055,29056,29058],{"class":114,"line":3680},[112,29057,9204],{"class":129},[112,29059,18566],{"class":136},[112,29061,29062,29064],{"class":114,"line":3692},[112,29063,9180],{"class":9141},[112,29065,5004],{"class":129},[112,29067,29068,29070,29072],{"class":114,"line":3716},[112,29069,15306],{"class":9141},[112,29071,567],{"class":129},[112,29073,15311],{"class":136},[112,29075,29076,29078,29080],{"class":114,"line":3737},[112,29077,15316],{"class":9141},[112,29079,567],{"class":129},[112,29081,29082],{"class":136},"root_password\n",[112,29084,29085,29087,29089],{"class":114,"line":3742},[112,29086,15325],{"class":9141},[112,29088,567],{"class":129},[112,29090,29091],{"class":136},"test_db\n",[112,29093,29094,29096,29098],{"class":114,"line":3747},[112,29095,15335],{"class":9141},[112,29097,567],{"class":129},[112,29099,29100],{"class":136},"test_user\n",[112,29102,29103,29105,29107],{"class":114,"line":3752},[112,29104,15345],{"class":9141},[112,29106,567],{"class":129},[112,29108,29109],{"class":136},"test_password\n",[11,29111,29113],{"id":29112},"typeorm設定","TypeORM設定",[15,29115,29116],{},[27,29117,29118],{},"⚠️ 警告：以下の設定を本番環境で使用するとデータが失われる可能性があります。必ずテスト専用の設定ファイルとして分離してください。",[103,29120,29122],{"className":105,"code":29121,"language":107,"meta":108,"style":108},"\u002F\u002F ormconfig.test.ts\nmodule.exports = {\n  type: 'mysql',\n  host: process.env.DB_HOST || 'localhost',\n  port: process.env.DB_PORT || '3306',\n  username: process.env.DB_USERNAME || 'test_user',\n  password: process.env.DB_PASSWORD || 'test_password',\n  database: process.env.DB_NAME || 'test_db',\n  \u002F\u002F テスト環境のみ有効化：アプリケーション起動時にスキーマを自動同期\n  synchronize: true,\n  \u002F\u002F 実行SQLをログ出力\n  logging: true,\n  entities: ['src\u002Fdomain\u002Fentities\u002F*.ts'],\n  migrations: ['src\u002Fdatabases\u002Fmigrations\u002F*.ts'],\n  seeds: ['src\u002Ftest\u002Fdatabases\u002Fseeders\u002F*.seed.{js,ts}'],\n  subscribers: ['src\u002Fsubscribers\u002F**\u002F*.ts'],\n  cli: {\n    migrationsDir: 'src\u002Fdatabases\u002Fmigrations',\n    entitiesDir: 'src\u002Fdomain\u002Fentities',\n    seedersDir: 'src\u002Fdatabases\u002Fseeders',\n    subscribersDir: 'src\u002Fsubscribers',\n  },\n}\n",[90,29123,29124,29128,29140,29148,29160,29172,29185,29198,29211,29216,29224,29229,29237,29246,29254,29264,29274,29278,29286,29295,29305,29315,29319],{"__ignoreMap":108},[112,29125,29126],{"class":114,"line":115},[112,29127,17413],{"class":118},[112,29129,29130,29132,29134,29136,29138],{"class":114,"line":122},[112,29131,9850],{"class":156},[112,29133,2124],{"class":129},[112,29135,16473],{"class":156},[112,29137,160],{"class":125},[112,29139,1294],{"class":129},[112,29141,29142,29144,29146],{"class":114,"line":143},[112,29143,173],{"class":129},[112,29145,15825],{"class":136},[112,29147,179],{"class":129},[112,29149,29150,29152,29154,29156,29158],{"class":114,"line":150},[112,29151,16490],{"class":129},[112,29153,15586],{"class":156},[112,29155,10852],{"class":125},[112,29157,15591],{"class":136},[112,29159,179],{"class":129},[112,29161,29162,29164,29166,29168,29170],{"class":114,"line":170},[112,29163,16503],{"class":129},[112,29165,15604],{"class":156},[112,29167,10852],{"class":125},[112,29169,16510],{"class":136},[112,29171,179],{"class":129},[112,29173,29174,29176,29178,29180,29183],{"class":114,"line":182},[112,29175,16517],{"class":129},[112,29177,15621],{"class":156},[112,29179,10852],{"class":125},[112,29181,29182],{"class":136}," 'test_user'",[112,29184,179],{"class":129},[112,29186,29187,29189,29191,29193,29196],{"class":114,"line":193},[112,29188,16530],{"class":129},[112,29190,15636],{"class":156},[112,29192,10852],{"class":125},[112,29194,29195],{"class":136}," 'test_password'",[112,29197,179],{"class":129},[112,29199,29200,29202,29204,29206,29209],{"class":114,"line":205},[112,29201,16543],{"class":129},[112,29203,15651],{"class":156},[112,29205,10852],{"class":125},[112,29207,29208],{"class":136}," 'test_db'",[112,29210,179],{"class":129},[112,29212,29213],{"class":114,"line":241},[112,29214,29215],{"class":118},"  \u002F\u002F テスト環境のみ有効化：アプリケーション起動時にスキーマを自動同期\n",[112,29217,29218,29220,29222],{"class":114,"line":247},[112,29219,16556],{"class":129},[112,29221,4345],{"class":156},[112,29223,179],{"class":129},[112,29225,29226],{"class":114,"line":351},[112,29227,29228],{"class":118},"  \u002F\u002F 実行SQLをログ出力\n",[112,29230,29231,29233,29235],{"class":114,"line":361},[112,29232,16565],{"class":129},[112,29234,4345],{"class":156},[112,29236,179],{"class":129},[112,29238,29239,29241,29244],{"class":114,"line":367},[112,29240,16574],{"class":129},[112,29242,29243],{"class":136},"'src\u002Fdomain\u002Fentities\u002F*.ts'",[112,29245,746],{"class":129},[112,29247,29248,29250,29252],{"class":114,"line":373},[112,29249,16584],{"class":129},[112,29251,16587],{"class":136},[112,29253,746],{"class":129},[112,29255,29256,29259,29262],{"class":114,"line":379},[112,29257,29258],{"class":129},"  seeds: [",[112,29260,29261],{"class":136},"'src\u002Ftest\u002Fdatabases\u002Fseeders\u002F*.seed.{js,ts}'",[112,29263,746],{"class":129},[112,29265,29266,29269,29272],{"class":114,"line":1302},[112,29267,29268],{"class":129},"  subscribers: [",[112,29270,29271],{"class":136},"'src\u002Fsubscribers\u002F**\u002F*.ts'",[112,29273,746],{"class":129},[112,29275,29276],{"class":114,"line":1502},[112,29277,16594],{"class":129},[112,29279,29280,29282,29284],{"class":114,"line":1507},[112,29281,16599],{"class":129},[112,29283,16602],{"class":136},[112,29285,179],{"class":129},[112,29287,29288,29290,29293],{"class":114,"line":1512},[112,29289,16609],{"class":129},[112,29291,29292],{"class":136},"'src\u002Fdomain\u002Fentities'",[112,29294,179],{"class":129},[112,29296,29297,29300,29303],{"class":114,"line":1518},[112,29298,29299],{"class":129},"    seedersDir: ",[112,29301,29302],{"class":136},"'src\u002Fdatabases\u002Fseeders'",[112,29304,179],{"class":129},[112,29306,29307,29310,29313],{"class":114,"line":1524},[112,29308,29309],{"class":129},"    subscribersDir: ",[112,29311,29312],{"class":136},"'src\u002Fsubscribers'",[112,29314,179],{"class":129},[112,29316,29317],{"class":114,"line":1530},[112,29318,376],{"class":129},[112,29320,29321],{"class":114,"line":1536},[112,29322,1452],{"class":129},[15,29324,18799,29325,29328,29329,29332],{},[90,29326,29327],{},"synchronize: true","は、Entity定義からテーブルを自動生成する便利な機能ですが、",[27,29330,29331],{},"本番環境では絶対に使用しないでください","。既存のテーブル構造が上書きされ、データ損失のリスクがあります。",[11,29334,29335],{"id":29335},"テスト実行",[83,29337,29338],{"id":29338},"ローカルでの実行",[103,29340,29342],{"className":771,"code":29341,"language":773,"meta":108,"style":108},"# コンテナをビルド\ndocker-compose -f unit-test.yml build\n\n# テストを実行（テスト終了後、コンテナを自動停止）\ndocker-compose -f unit-test.yml up --abort-on-container-exit\n",[90,29343,29344,29349,29362,29366,29371],{"__ignoreMap":108},[112,29345,29346],{"class":114,"line":115},[112,29347,29348],{"class":118},"# コンテナをビルド\n",[112,29350,29351,29354,29357,29360],{"class":114,"line":122},[112,29352,29353],{"class":163},"docker-compose",[112,29355,29356],{"class":156}," -f",[112,29358,29359],{"class":136}," unit-test.yml",[112,29361,16644],{"class":136},[112,29363,29364],{"class":114,"line":143},[112,29365,147],{"emptyLinePlaceholder":146},[112,29367,29368],{"class":114,"line":150},[112,29369,29370],{"class":118},"# テストを実行（テスト終了後、コンテナを自動停止）\n",[112,29372,29373,29375,29377,29379,29381],{"class":114,"line":170},[112,29374,29353],{"class":163},[112,29376,29356],{"class":156},[112,29378,29359],{"class":136},[112,29380,15386],{"class":136},[112,29382,29383],{"class":156}," --abort-on-container-exit\n",[83,29385,29386],{"id":29386},"実行結果例",[103,29388,29391],{"className":29389,"code":29390,"language":5951},[5949],"Recreating redis_container ... done\nRecreating db_container    ... done\nRecreating app-test ... done\nAttaching to redis_container-test, db_container_test, app-test\n\napp-test |\napp-test | > sample@0.0.1 test:e2e \u002Fwork\napp-test | > jest --config .\u002Fsrc\u002Ftest\u002Fe2e\u002Fjest-e2e.json\napp-test |\napp-test | PASS src\u002Ftest\u002Fe2e\u002Fcontractor-reps.e2e-spec.ts (92.288 s)\napp-test   契約担当者(E2E)\napp-test     ログインしていない場合は401が返ります\napp-test       ✓ OK \u002Fcontractor-reps (GET) (1471 ms)\n...\n\napp-test Test Suites: 1 passed, 1 total\napp-test Tests:       38 passed, 38 total\napp-test Time:        92.428 s\napp-test exited with code 0\n",[90,29392,29390],{"__ignoreMap":108},[11,29394,29396],{"id":29395},"github-actions設定","GitHub Actions設定",[15,29398,29399],{},"プルリクエストのたびに自動でテストを実行するワークフローを設定します。",[103,29401,29403],{"className":9132,"code":29402,"language":9134,"meta":108,"style":108},"# .github\u002Fworkflows\u002Ftest.yml\nname: E2E Tests\n\non:\n  pull_request:\n    types: [opened, synchronize]\n\njobs:\n  run-test:\n    name: Run E2E Tests\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions\u002Fcheckout@v2\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n\n      - name: Run tests with docker-compose\n        run: |\n          docker-compose -f .\u002Funit-test.yml build\n          docker-compose -f .\u002Funit-test.yml up --abort-on-container-exit\n        working-directory: .\u002F\n",[90,29404,29405,29410,29418,29422,29428,29434,29450,29454,29460,29466,29474,29482,29486,29492,29502,29510,29516,29526,29530,29540,29548,29552,29556],{"__ignoreMap":108},[112,29406,29407],{"class":114,"line":115},[112,29408,29409],{"class":118},"# .github\u002Fworkflows\u002Ftest.yml\n",[112,29411,29412,29414,29416],{"class":114,"line":122},[112,29413,16383],{"class":9141},[112,29415,567],{"class":129},[112,29417,19381],{"class":136},[112,29419,29420],{"class":114,"line":143},[112,29421,147],{"emptyLinePlaceholder":146},[112,29423,29424,29426],{"class":114,"line":150},[112,29425,18628],{"class":156},[112,29427,5004],{"class":129},[112,29429,29430,29432],{"class":114,"line":170},[112,29431,19415],{"class":9141},[112,29433,5004],{"class":129},[112,29435,29436,29439,29441,29444,29446,29448],{"class":114,"line":182},[112,29437,29438],{"class":9141},"    types",[112,29440,19404],{"class":129},[112,29442,29443],{"class":136},"opened",[112,29445,447],{"class":129},[112,29447,18802],{"class":136},[112,29449,19410],{"class":129},[112,29451,29452],{"class":114,"line":193},[112,29453,147],{"emptyLinePlaceholder":146},[112,29455,29456,29458],{"class":114,"line":205},[112,29457,18660],{"class":9141},[112,29459,5004],{"class":129},[112,29461,29462,29464],{"class":114,"line":241},[112,29463,18667],{"class":9141},[112,29465,5004],{"class":129},[112,29467,29468,29470,29472],{"class":114,"line":247},[112,29469,18674],{"class":9141},[112,29471,567],{"class":129},[112,29473,18619],{"class":136},[112,29475,29476,29478,29480],{"class":114,"line":351},[112,29477,18683],{"class":9141},[112,29479,567],{"class":129},[112,29481,18688],{"class":136},[112,29483,29484],{"class":114,"line":361},[112,29485,147],{"emptyLinePlaceholder":146},[112,29487,29488,29490],{"class":114,"line":367},[112,29489,18697],{"class":9141},[112,29491,5004],{"class":129},[112,29493,29494,29496,29498,29500],{"class":114,"line":373},[112,29495,9204],{"class":129},[112,29497,16383],{"class":9141},[112,29499,567],{"class":129},[112,29501,18710],{"class":136},[112,29503,29504,29506,29508],{"class":114,"line":379},[112,29505,18715],{"class":9141},[112,29507,567],{"class":129},[112,29509,18720],{"class":136},[112,29511,29512,29514],{"class":114,"line":1302},[112,29513,19499],{"class":9141},[112,29515,5004],{"class":129},[112,29517,29518,29521,29523],{"class":114,"line":1502},[112,29519,29520],{"class":9141},"          ref",[112,29522,567],{"class":129},[112,29524,29525],{"class":136},"${{ github.event.pull_request.head.sha }}\n",[112,29527,29528],{"class":114,"line":1507},[112,29529,147],{"emptyLinePlaceholder":146},[112,29531,29532,29534,29536,29538],{"class":114,"line":1512},[112,29533,9204],{"class":129},[112,29535,16383],{"class":9141},[112,29537,567],{"class":129},[112,29539,18735],{"class":136},[112,29541,29542,29544,29546],{"class":114,"line":1518},[112,29543,18740],{"class":9141},[112,29545,567],{"class":129},[112,29547,18745],{"class":125},[112,29549,29550],{"class":114,"line":1524},[112,29551,18750],{"class":136},[112,29553,29554],{"class":114,"line":1530},[112,29555,18755],{"class":136},[112,29557,29558,29560,29562],{"class":114,"line":1536},[112,29559,18760],{"class":9141},[112,29561,567],{"class":129},[112,29563,18765],{"class":136},[11,29565,919],{"id":919},[15,29567,29568],{},"docker-composeとGitHub Actionsを組み合わせることで、以下のメリットが得られます。",[31,29570,29571,29577,29583],{},[34,29572,29573,29576],{},[27,29574,29575],{},"環境の一貫性"," 本番環境に近い構成でテストを実行",[34,29578,29579,29582],{},[27,29580,29581],{},"自動化"," プルリクエストごとに自動テスト実行",[34,29584,29585,29588],{},[27,29586,29587],{},"早期発見"," 統合不具合を早期に検出",[15,29590,29591,29594],{},[90,29592,29593],{},"--abort-on-container-exit","フラグを使用することで、テスト完了後にコンテナが自動停止し、CI\u002FCDパイプラインが効率的に終了します。",[944,29596,29597],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":108,"searchDepth":122,"depth":122,"links":29599},[29600,29601,29605,29606,29607,29611,29612],{"id":13,"depth":122,"text":13},{"id":28665,"depth":122,"text":28666,"children":29602},[29603,29604],{"id":28669,"depth":143,"text":28670},{"id":28719,"depth":143,"text":28720},{"id":12891,"depth":122,"text":12891},{"id":29112,"depth":122,"text":29113},{"id":29335,"depth":122,"text":29335,"children":29608},[29609,29610],{"id":29338,"depth":143,"text":29338},{"id":29386,"depth":143,"text":29386},{"id":29395,"depth":122,"text":29396},{"id":919,"depth":122,"text":919},"2021-11-28","docker-composeとGitHub Actionsを組み合わせて、E2Eテストを自動実行するCI\u002FCD環境を構築します。TypeORMのsynchronize機能を活用したテスト環境構築の実例も紹介します。",{"tags":29616},[15380,18866,29617,18869,26492],"cicd","\u002Fblog\u002Fdocker-compose-github-actions-cicd",{"title":28651,"description":29614},"blog\u002Fdocker-compose-github-actions-cicd","dUcYkPYbItEkqkxqgKrQAt32_LsRkhbKAWJ8qOJAeY8",{"id":29623,"title":29624,"body":29625,"date":29613,"description":31599,"extension":962,"meta":31600,"navigation":146,"path":28660,"seo":31602,"stem":31603,"__hash__":31604},"blog\u002Fblog\u002Fjest-nestjs-e2e-testing.md","NestJS + JestでE2Eテスト環境を構築する",{"type":8,"value":29626,"toc":31576},[29627,29629,29632,29635,29641,29645,29647,29650,29763,29766,29792,29799,29801,29809,29999,30008,30015,30017,30034,30041,30045,30048,30051,30057,30060,30149,30151,30154,30545,30548,31082,31085,31509,31511,31523,31525,31531,31535,31540,31542,31545,31570,31573],[11,29628,13],{"id":13},[15,29630,29631],{},"NestJSでJestを使ったE2Eテストを導入した際の知見をまとめます。",[11,29633,29634],{"id":29634},"ディレクトリ構造",[103,29636,29639],{"className":29637,"code":29638,"language":5951},[5949],"e2e\u002F\n├── users.e2e-spec.ts\n├── jest-e2e.json\n├── mocks\u002F\n│   └── contractorReps\u002F\n│       └── mock.ts\n└── utilities\u002F\n    ├── common.utility.ts\n    └── master.utility.ts\n",[90,29640,29638],{"__ignoreMap":108},[11,29642,29644],{"id":29643},"jest設定","Jest設定",[83,29646,15493],{"id":15493},[15,29648,29649],{},"E2Eテスト用のJest設定を作成します。",[103,29651,29653],{"className":2956,"code":29652,"language":2958,"meta":108,"style":108},"{\n  \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n  \"rootDir\": \".\",\n  \"testEnvironment\": \"node\",\n  \"testRegex\": \".e2e-spec.ts$\",\n  \"transform\": {\n    \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n  },\n  \"moduleNameMapper\": {\n    \"^src\u002F(.*)$\": \"\u003CrootDir>\u002F..\u002F..\u002F$1\"\n  }\n}\n",[90,29654,29655,29659,29681,29693,29705,29717,29724,29734,29738,29745,29755,29759],{"__ignoreMap":108},[112,29656,29657],{"class":114,"line":115},[112,29658,2965],{"class":129},[112,29660,29661,29664,29666,29669,29671,29674,29676,29679],{"class":114,"line":122},[112,29662,29663],{"class":156},"  \"moduleFileExtensions\"",[112,29665,19404],{"class":129},[112,29667,29668],{"class":136},"\"js\"",[112,29670,447],{"class":129},[112,29672,29673],{"class":136},"\"json\"",[112,29675,447],{"class":129},[112,29677,29678],{"class":136},"\"ts\"",[112,29680,746],{"class":129},[112,29682,29683,29686,29688,29691],{"class":114,"line":143},[112,29684,29685],{"class":156},"  \"rootDir\"",[112,29687,567],{"class":129},[112,29689,29690],{"class":136},"\".\"",[112,29692,179],{"class":129},[112,29694,29695,29698,29700,29703],{"class":114,"line":150},[112,29696,29697],{"class":156},"  \"testEnvironment\"",[112,29699,567],{"class":129},[112,29701,29702],{"class":136},"\"node\"",[112,29704,179],{"class":129},[112,29706,29707,29710,29712,29715],{"class":114,"line":170},[112,29708,29709],{"class":156},"  \"testRegex\"",[112,29711,567],{"class":129},[112,29713,29714],{"class":136},"\".e2e-spec.ts$\"",[112,29716,179],{"class":129},[112,29718,29719,29722],{"class":114,"line":182},[112,29720,29721],{"class":156},"  \"transform\"",[112,29723,852],{"class":129},[112,29725,29726,29729,29731],{"class":114,"line":193},[112,29727,29728],{"class":156},"    \"^.+\\\\.(t|j)s$\"",[112,29730,567],{"class":129},[112,29732,29733],{"class":136},"\"ts-jest\"\n",[112,29735,29736],{"class":114,"line":205},[112,29737,376],{"class":129},[112,29739,29740,29743],{"class":114,"line":241},[112,29741,29742],{"class":156},"  \"moduleNameMapper\"",[112,29744,852],{"class":129},[112,29746,29747,29750,29752],{"class":114,"line":247},[112,29748,29749],{"class":156},"    \"^src\u002F(.*)$\"",[112,29751,567],{"class":129},[112,29753,29754],{"class":136},"\"\u003CrootDir>\u002F..\u002F..\u002F$1\"\n",[112,29756,29757],{"class":114,"line":351},[112,29758,3232],{"class":129},[112,29760,29761],{"class":114,"line":361},[112,29762,1452],{"class":129},[15,29764,29765],{},"主な設定項目：",[31,29767,29768,29774,29780,29786],{},[34,29769,29770,29773],{},[90,29771,29772],{},"moduleFileExtensions",": 使用するファイル拡張子",[34,29775,29776,29779],{},[90,29777,29778],{},"testEnvironment",": テスト実行環境",[34,29781,29782,29785],{},[90,29783,29784],{},"testRegex",": テストファイルの検出パターン",[34,29787,29788,29791],{},[90,29789,29790],{},"moduleNameMapper",": パスエイリアスの解決",[15,29793,7082,29794,7088],{},[759,29795,29798],{"href":29796,"rel":29797},"https:\u002F\u002Fjestjs.io\u002Fja\u002Fdocs\u002Fconfiguration#modulenamemapper-objectstring-string--arraystring",[763],"Jest公式ドキュメント",[11,29800,29113],{"id":29112},[15,29802,29803],{},[27,29804,29805,29806,29808],{},"⚠️ 警告：",[90,29807,29327],{},"は本番環境で使用しないでください。既存データが失われる可能性があります。",[103,29810,29812],{"className":105,"code":29811,"language":107,"meta":108,"style":108},"\u002F\u002F ormconfig.test.ts\nmodule.exports = {\n  type: 'mysql',\n  host: process.env.DB_HOST || 'localhost',\n  port: process.env.DB_PORT || '3306',\n  username: process.env.DB_USERNAME || 'test_user',\n  password: process.env.DB_PASSWORD || 'test_password',\n  database: process.env.DB_NAME || 'test_db',\n  \u002F\u002F テスト環境のみ有効化：Entity定義からテーブルを自動生成\n  synchronize: true,\n  \u002F\u002F 実行SQLをログ出力\n  logging: true,\n  entities: ['src\u002Fdomain\u002Fentities\u002F*.ts'],\n  migrations: ['src\u002Fdatabases\u002Fmigrations\u002F*.ts'],\n  seeds: ['src\u002Ftest\u002Fdatabases\u002Fseeders\u002F*.seed.{js,ts}'],\n  subscribers: ['src\u002Fsubscribers\u002F**\u002F*.ts'],\n  cli: {\n    migrationsDir: 'src\u002Fdatabases\u002Fmigrations',\n    entitiesDir: 'src\u002Fdomain\u002Fentities',\n    seedersDir: 'src\u002Fdatabases\u002Fseeders',\n    subscribersDir: 'src\u002Fsubscribers',\n  },\n}\n",[90,29813,29814,29818,29830,29838,29850,29862,29874,29886,29898,29903,29911,29915,29923,29931,29939,29947,29955,29959,29967,29975,29983,29991,29995],{"__ignoreMap":108},[112,29815,29816],{"class":114,"line":115},[112,29817,17413],{"class":118},[112,29819,29820,29822,29824,29826,29828],{"class":114,"line":122},[112,29821,9850],{"class":156},[112,29823,2124],{"class":129},[112,29825,16473],{"class":156},[112,29827,160],{"class":125},[112,29829,1294],{"class":129},[112,29831,29832,29834,29836],{"class":114,"line":143},[112,29833,173],{"class":129},[112,29835,15825],{"class":136},[112,29837,179],{"class":129},[112,29839,29840,29842,29844,29846,29848],{"class":114,"line":150},[112,29841,16490],{"class":129},[112,29843,15586],{"class":156},[112,29845,10852],{"class":125},[112,29847,15591],{"class":136},[112,29849,179],{"class":129},[112,29851,29852,29854,29856,29858,29860],{"class":114,"line":170},[112,29853,16503],{"class":129},[112,29855,15604],{"class":156},[112,29857,10852],{"class":125},[112,29859,16510],{"class":136},[112,29861,179],{"class":129},[112,29863,29864,29866,29868,29870,29872],{"class":114,"line":182},[112,29865,16517],{"class":129},[112,29867,15621],{"class":156},[112,29869,10852],{"class":125},[112,29871,29182],{"class":136},[112,29873,179],{"class":129},[112,29875,29876,29878,29880,29882,29884],{"class":114,"line":193},[112,29877,16530],{"class":129},[112,29879,15636],{"class":156},[112,29881,10852],{"class":125},[112,29883,29195],{"class":136},[112,29885,179],{"class":129},[112,29887,29888,29890,29892,29894,29896],{"class":114,"line":205},[112,29889,16543],{"class":129},[112,29891,15651],{"class":156},[112,29893,10852],{"class":125},[112,29895,29208],{"class":136},[112,29897,179],{"class":129},[112,29899,29900],{"class":114,"line":241},[112,29901,29902],{"class":118},"  \u002F\u002F テスト環境のみ有効化：Entity定義からテーブルを自動生成\n",[112,29904,29905,29907,29909],{"class":114,"line":247},[112,29906,16556],{"class":129},[112,29908,4345],{"class":156},[112,29910,179],{"class":129},[112,29912,29913],{"class":114,"line":351},[112,29914,29228],{"class":118},[112,29916,29917,29919,29921],{"class":114,"line":361},[112,29918,16565],{"class":129},[112,29920,4345],{"class":156},[112,29922,179],{"class":129},[112,29924,29925,29927,29929],{"class":114,"line":367},[112,29926,16574],{"class":129},[112,29928,29243],{"class":136},[112,29930,746],{"class":129},[112,29932,29933,29935,29937],{"class":114,"line":373},[112,29934,16584],{"class":129},[112,29936,16587],{"class":136},[112,29938,746],{"class":129},[112,29940,29941,29943,29945],{"class":114,"line":379},[112,29942,29258],{"class":129},[112,29944,29261],{"class":136},[112,29946,746],{"class":129},[112,29948,29949,29951,29953],{"class":114,"line":1302},[112,29950,29268],{"class":129},[112,29952,29271],{"class":136},[112,29954,746],{"class":129},[112,29956,29957],{"class":114,"line":1502},[112,29958,16594],{"class":129},[112,29960,29961,29963,29965],{"class":114,"line":1507},[112,29962,16599],{"class":129},[112,29964,16602],{"class":136},[112,29966,179],{"class":129},[112,29968,29969,29971,29973],{"class":114,"line":1512},[112,29970,16609],{"class":129},[112,29972,29292],{"class":136},[112,29974,179],{"class":129},[112,29976,29977,29979,29981],{"class":114,"line":1518},[112,29978,29299],{"class":129},[112,29980,29302],{"class":136},[112,29982,179],{"class":129},[112,29984,29985,29987,29989],{"class":114,"line":1524},[112,29986,29309],{"class":129},[112,29988,29312],{"class":136},[112,29990,179],{"class":129},[112,29992,29993],{"class":114,"line":1530},[112,29994,376],{"class":129},[112,29996,29997],{"class":114,"line":1536},[112,29998,1452],{"class":129},[15,30000,30001,30003,30004,30007],{},[90,30002,18802],{},"オプションは、アプリケーション起動時にEntity定義からテーブルを自動生成する便利な機能ですが、既存のテーブル構造を上書きするため、",[27,30005,30006],{},"テスト環境専用","としてください。",[15,30009,7082,30010,7088],{},[759,30011,30014],{"href":30012,"rel":30013},"https:\u002F\u002Fgithub.com\u002Ftypeorm\u002Ftypeorm\u002Fblob\u002Fmaster\u002Fdocs\u002Ffaq.md",[763],"TypeORM FAQ",[11,30016,15456],{"id":15456},[103,30018,30020],{"className":771,"code":30019,"language":773,"meta":108,"style":108},"npm install --save-dev @nestjs\u002Ftesting\n",[90,30021,30022],{"__ignoreMap":108},[112,30023,30024,30026,30028,30031],{"class":114,"line":115},[112,30025,6832],{"class":163},[112,30027,7883],{"class":136},[112,30029,30030],{"class":156}," --save-dev",[112,30032,30033],{"class":136}," @nestjs\u002Ftesting\n",[15,30035,8011,30036],{},[759,30037,30040],{"href":30038,"rel":30039},"https:\u002F\u002Fdocs.nestjs.com\u002Ffundamentals\u002Ftesting",[763],"NestJS Testing",[11,30042,30044],{"id":30043},"テスト対象のapi仕様","テスト対象のAPI仕様",[15,30046,30047],{},"以下のユーザー情報取得APIをテストします。",[83,30049,30050],{"id":30050},"リクエスト例",[103,30052,30055],{"className":30053,"code":30054,"language":5951},[5949],"GET http:\u002F\u002Flocalhost:3000\u002Fusers?name=テスト\nAuthorization: Bearer \u003Ctoken>\n",[90,30056,30054],{"__ignoreMap":108},[83,30058,30059],{"id":30059},"レスポンス例",[103,30061,30063],{"className":2956,"code":30062,"language":2958,"meta":108,"style":108},"{\n  \"statusCode\": 200,\n  \"message\": \"SUCCESS\",\n  \"data\": [\n    {\n      \"id\": 1,\n      \"name\": \"テスト\",\n      \"ins_ts\": \"2021\u002F11\u002F29 13:47\"\n    }\n  ]\n}\n",[90,30064,30065,30069,30080,30092,30099,30103,30114,30126,30136,30140,30145],{"__ignoreMap":108},[112,30066,30067],{"class":114,"line":115},[112,30068,2965],{"class":129},[112,30070,30071,30074,30076,30078],{"class":114,"line":122},[112,30072,30073],{"class":156},"  \"statusCode\"",[112,30075,567],{"class":129},[112,30077,20551],{"class":156},[112,30079,179],{"class":129},[112,30081,30082,30085,30087,30090],{"class":114,"line":143},[112,30083,30084],{"class":156},"  \"message\"",[112,30086,567],{"class":129},[112,30088,30089],{"class":136},"\"SUCCESS\"",[112,30091,179],{"class":129},[112,30093,30094,30097],{"class":114,"line":150},[112,30095,30096],{"class":156},"  \"data\"",[112,30098,3021],{"class":129},[112,30100,30101],{"class":114,"line":170},[112,30102,2309],{"class":129},[112,30104,30105,30108,30110,30112],{"class":114,"line":182},[112,30106,30107],{"class":156},"      \"id\"",[112,30109,567],{"class":129},[112,30111,8179],{"class":156},[112,30113,179],{"class":129},[112,30115,30116,30119,30121,30124],{"class":114,"line":193},[112,30117,30118],{"class":156},"      \"name\"",[112,30120,567],{"class":129},[112,30122,30123],{"class":136},"\"テスト\"",[112,30125,179],{"class":129},[112,30127,30128,30131,30133],{"class":114,"line":205},[112,30129,30130],{"class":156},"      \"ins_ts\"",[112,30132,567],{"class":129},[112,30134,30135],{"class":136},"\"2021\u002F11\u002F29 13:47\"\n",[112,30137,30138],{"class":114,"line":241},[112,30139,3118],{"class":129},[112,30141,30142],{"class":114,"line":247},[112,30143,30144],{"class":129},"  ]\n",[112,30146,30147],{"class":114,"line":351},[112,30148,1452],{"class":129},[11,30150,17545],{"id":17544},[83,30152,30153],{"id":30153},"テストモジュールのセットアップ",[103,30155,30157],{"className":105,"code":30156,"language":107,"meta":108,"style":108},"import { INestApplication, ValidationPipe } from '@nestjs\u002Fcommon'\nimport { ConfigModule } from '@nestjs\u002Fconfig'\nimport { APP_GUARD } from '@nestjs\u002Fcore'\nimport { Test, TestingModule } from '@nestjs\u002Ftesting'\nimport { TypeOrmModule } from '@nestjs\u002Ftypeorm'\nimport * as request from 'supertest'\n\ndescribe('ユーザーAPI (E2E)', () => {\n  let app: INestApplication\n\n  \u002F\u002F 各テスト実行前にデータをリセット\n  beforeEach(async () => {\n    await useRefreshDatabase()\n    await runTestDataSeeder()\n  })\n\n  \u002F\u002F テスト開始前に1回だけ実行\n  beforeAll(async () => {\n    const moduleFixture: TestingModule = await Test.createTestingModule({\n      imports: [\n        TypeOrmModule.forFeature([User]),\n        ConfigModule.forRoot({\n          envFilePath: ENV_FILE_PATH,\n        }),\n        AppModule,\n      ],\n      controllers: [UserController],\n      providers: [\n        UserService,\n        {\n          provide: APP_GUARD,\n          useExisting: RoleGuard,\n        },\n        RoleGuard,\n      ],\n    }).compile()\n\n    app = moduleFixture.createNestApplication()\n    app.useGlobalPipes(new ValidationPipe())\n    await app.init()\n  })\n\n  \u002F\u002F テスト終了後にクリーンアップ\n  afterAll(async () => {\n    await app.close()\n    await tearDownDatabase()\n  })\n\n  \u002F\u002F ... テストケース\n})\n",[90,30158,30159,30169,30179,30190,30200,30210,30224,30228,30243,30253,30257,30262,30276,30284,30293,30297,30301,30306,30320,30340,30345,30355,30364,30374,30379,30384,30388,30393,30397,30402,30406,30416,30421,30425,30430,30434,30442,30446,30458,30472,30482,30486,30490,30495,30509,30519,30528,30532,30536,30541],{"__ignoreMap":108},[112,30160,30161,30163,30165,30167],{"class":114,"line":115},[112,30162,126],{"class":125},[112,30164,17579],{"class":129},[112,30166,133],{"class":125},[112,30168,9868],{"class":136},[112,30170,30171,30173,30175,30177],{"class":114,"line":122},[112,30172,126],{"class":125},[112,30174,9875],{"class":129},[112,30176,133],{"class":125},[112,30178,9880],{"class":136},[112,30180,30181,30183,30186,30188],{"class":114,"line":143},[112,30182,126],{"class":125},[112,30184,30185],{"class":129}," { APP_GUARD } ",[112,30187,133],{"class":125},[112,30189,13246],{"class":136},[112,30191,30192,30194,30196,30198],{"class":114,"line":150},[112,30193,126],{"class":125},[112,30195,12367],{"class":129},[112,30197,133],{"class":125},[112,30199,12372],{"class":136},[112,30201,30202,30204,30206,30208],{"class":114,"line":170},[112,30203,126],{"class":125},[112,30205,15707],{"class":129},[112,30207,133],{"class":125},[112,30209,15712],{"class":136},[112,30211,30212,30214,30216,30218,30220,30222],{"class":114,"line":182},[112,30213,126],{"class":125},[112,30215,17049],{"class":156},[112,30217,17052],{"class":125},[112,30219,17614],{"class":129},[112,30221,133],{"class":125},[112,30223,17619],{"class":136},[112,30225,30226],{"class":114,"line":193},[112,30227,147],{"emptyLinePlaceholder":146},[112,30229,30230,30232,30234,30237,30239,30241],{"class":114,"line":205},[112,30231,12401],{"class":163},[112,30233,425],{"class":129},[112,30235,30236],{"class":136},"'ユーザーAPI (E2E)'",[112,30238,595],{"class":129},[112,30240,229],{"class":125},[112,30242,1294],{"class":129},[112,30244,30245,30247,30249,30251],{"class":114,"line":241},[112,30246,12417],{"class":125},[112,30248,14060],{"class":129},[112,30250,2243],{"class":125},[112,30252,17694],{"class":163},[112,30254,30255],{"class":114,"line":247},[112,30256,147],{"emptyLinePlaceholder":146},[112,30258,30259],{"class":114,"line":351},[112,30260,30261],{"class":118},"  \u002F\u002F 各テスト実行前にデータをリセット\n",[112,30263,30264,30266,30268,30270,30272,30274],{"class":114,"line":361},[112,30265,12538],{"class":163},[112,30267,425],{"class":129},[112,30269,3305],{"class":125},[112,30271,3802],{"class":129},[112,30273,229],{"class":125},[112,30275,1294],{"class":129},[112,30277,30278,30280,30282],{"class":114,"line":367},[112,30279,3385],{"class":125},[112,30281,17719],{"class":163},[112,30283,630],{"class":129},[112,30285,30286,30288,30291],{"class":114,"line":373},[112,30287,3385],{"class":125},[112,30289,30290],{"class":163}," runTestDataSeeder",[112,30292,630],{"class":129},[112,30294,30295],{"class":114,"line":379},[112,30296,8004],{"class":129},[112,30298,30299],{"class":114,"line":1302},[112,30300,147],{"emptyLinePlaceholder":146},[112,30302,30303],{"class":114,"line":1502},[112,30304,30305],{"class":118},"  \u002F\u002F テスト開始前に1回だけ実行\n",[112,30307,30308,30310,30312,30314,30316,30318],{"class":114,"line":1507},[112,30309,17734],{"class":163},[112,30311,425],{"class":129},[112,30313,3305],{"class":125},[112,30315,3802],{"class":129},[112,30317,229],{"class":125},[112,30319,1294],{"class":129},[112,30321,30322,30324,30326,30328,30330,30332,30334,30336,30338],{"class":114,"line":1512},[112,30323,3472],{"class":125},[112,30325,17751],{"class":156},[112,30327,2243],{"class":125},[112,30329,12560],{"class":163},[112,30331,160],{"class":125},[112,30333,419],{"class":125},[112,30335,12567],{"class":129},[112,30337,12570],{"class":163},[112,30339,167],{"class":129},[112,30341,30342],{"class":114,"line":1518},[112,30343,30344],{"class":129},"      imports: [\n",[112,30346,30347,30350,30352],{"class":114,"line":1524},[112,30348,30349],{"class":129},"        TypeOrmModule.",[112,30351,17773],{"class":163},[112,30353,30354],{"class":129},"([User]),\n",[112,30356,30357,30360,30362],{"class":114,"line":1530},[112,30358,30359],{"class":129},"        ConfigModule.",[112,30361,15761],{"class":163},[112,30363,167],{"class":129},[112,30365,30366,30369,30372],{"class":114,"line":1536},[112,30367,30368],{"class":129},"          envFilePath: ",[112,30370,30371],{"class":156},"ENV_FILE_PATH",[112,30373,179],{"class":129},[112,30375,30376],{"class":114,"line":1542},[112,30377,30378],{"class":129},"        }),\n",[112,30380,30381],{"class":114,"line":2402},[112,30382,30383],{"class":129},"        AppModule,\n",[112,30385,30386],{"class":114,"line":2407},[112,30387,12606],{"class":129},[112,30389,30390],{"class":114,"line":2413},[112,30391,30392],{"class":129},"      controllers: [UserController],\n",[112,30394,30395],{"class":114,"line":2446},[112,30396,12577],{"class":129},[112,30398,30399],{"class":114,"line":2451},[112,30400,30401],{"class":129},"        UserService,\n",[112,30403,30404],{"class":114,"line":2464},[112,30405,12587],{"class":129},[112,30407,30408,30411,30414],{"class":114,"line":2481},[112,30409,30410],{"class":129},"          provide: ",[112,30412,30413],{"class":156},"APP_GUARD",[112,30415,179],{"class":129},[112,30417,30418],{"class":114,"line":2486},[112,30419,30420],{"class":129},"          useExisting: RoleGuard,\n",[112,30422,30423],{"class":114,"line":3229},[112,30424,2348],{"class":129},[112,30426,30427],{"class":114,"line":3235},[112,30428,30429],{"class":129},"        RoleGuard,\n",[112,30431,30432],{"class":114,"line":3657},[112,30433,12606],{"class":129},[112,30435,30436,30438,30440],{"class":114,"line":3662},[112,30437,12611],{"class":129},[112,30439,12614],{"class":163},[112,30441,630],{"class":129},[112,30443,30444],{"class":114,"line":3667},[112,30445,147],{"emptyLinePlaceholder":146},[112,30447,30448,30450,30452,30454,30456],{"class":114,"line":3680},[112,30449,17793],{"class":129},[112,30451,576],{"class":125},[112,30453,17798],{"class":129},[112,30455,17801],{"class":163},[112,30457,630],{"class":129},[112,30459,30460,30462,30464,30466,30468,30470],{"class":114,"line":3692},[112,30461,17808],{"class":129},[112,30463,17811],{"class":163},[112,30465,425],{"class":129},[112,30467,14155],{"class":125},[112,30469,17818],{"class":163},[112,30471,17821],{"class":129},[112,30473,30474,30476,30478,30480],{"class":114,"line":3716},[112,30475,3385],{"class":125},[112,30477,14109],{"class":129},[112,30479,4115],{"class":163},[112,30481,630],{"class":129},[112,30483,30484],{"class":114,"line":3737},[112,30485,8004],{"class":129},[112,30487,30488],{"class":114,"line":3742},[112,30489,147],{"emptyLinePlaceholder":146},[112,30491,30492],{"class":114,"line":3747},[112,30493,30494],{"class":118},"  \u002F\u002F テスト終了後にクリーンアップ\n",[112,30496,30497,30499,30501,30503,30505,30507],{"class":114,"line":3752},[112,30498,17844],{"class":163},[112,30500,425],{"class":129},[112,30502,3305],{"class":125},[112,30504,3802],{"class":129},[112,30506,229],{"class":125},[112,30508,1294],{"class":129},[112,30510,30511,30513,30515,30517],{"class":114,"line":3758},[112,30512,3385],{"class":125},[112,30514,14109],{"class":129},[112,30516,17863],{"class":163},[112,30518,630],{"class":129},[112,30520,30521,30523,30526],{"class":114,"line":3778},[112,30522,3385],{"class":125},[112,30524,30525],{"class":163}," tearDownDatabase",[112,30527,630],{"class":129},[112,30529,30530],{"class":114,"line":3787},[112,30531,8004],{"class":129},[112,30533,30534],{"class":114,"line":3809},[112,30535,147],{"emptyLinePlaceholder":146},[112,30537,30538],{"class":114,"line":3827},[112,30539,30540],{"class":118},"  \u002F\u002F ... テストケース\n",[112,30542,30543],{"class":114,"line":3834},[112,30544,8436],{"class":129},[83,30546,30547],{"id":30547},"ヘルパー関数",[103,30549,30551],{"className":105,"code":30550,"language":107,"meta":108,"style":108},"\u002F**\n * ユーザー一覧を取得\n *\u002F\nconst index = async (account?: E2eLoginData): Promise\u003Crequest.Response> => {\n  const req = request(app.getHttpServer()).get(API_END_POINTS.USER)\n\n  if (account) {\n    req.set('Authorization', `Bearer ${await getJwtToken(request, app, account)}`)\n  }\n\n  return await req\n}\n\n\u002F**\n * ユーザーを取得\n *\u002F\nconst show = async (\n  id: number,\n  account?: E2eLoginData\n): Promise\u003Crequest.Response> => {\n  const req = request(app.getHttpServer()).get(`${API_END_POINTS.USER}\u002F${id}`)\n\n  if (account) {\n    req.set('Authorization', `Bearer ${await getJwtToken(request, app, account)}`)\n  }\n\n  return await req\n}\n\n\u002F**\n * ユーザーを作成\n *\u002F\nconst create = async (\n  dto: CreateUserDto,\n  account?: E2eLoginData\n): Promise\u003Crequest.Response> => {\n  const req = request(app.getHttpServer())\n    .post(API_END_POINTS.USER)\n    .set('Accept', 'application\u002Fjson')\n    .send(dto)\n\n  if (account) {\n    req.set('Authorization', `Bearer ${await getJwtToken(request, app, account)}`)\n  }\n\n  return await req\n}\n",[90,30552,30553,30558,30563,30568,30611,30641,30645,30652,30692,30696,30700,30709,30713,30717,30721,30726,30730,30744,30755,30765,30787,30824,30828,30834,30870,30874,30878,30886,30890,30894,30898,30903,30907,30919,30930,30938,30960,30976,30992,31008,31016,31020,31026,31062,31066,31070,31078],{"__ignoreMap":108},[112,30554,30555],{"class":114,"line":115},[112,30556,30557],{"class":118},"\u002F**\n",[112,30559,30560],{"class":114,"line":122},[112,30561,30562],{"class":118}," * ユーザー一覧を取得\n",[112,30564,30565],{"class":114,"line":143},[112,30566,30567],{"class":118}," *\u002F\n",[112,30569,30570,30572,30575,30577,30579,30581,30584,30586,30589,30591,30593,30595,30597,30599,30601,30604,30607,30609],{"class":114,"line":150},[112,30571,153],{"class":125},[112,30573,30574],{"class":163}," index",[112,30576,160],{"class":125},[112,30578,1955],{"class":125},[112,30580,3945],{"class":129},[112,30582,30583],{"class":222},"account",[112,30585,10023],{"class":125},[112,30587,30588],{"class":163}," E2eLoginData",[112,30590,10186],{"class":129},[112,30592,2243],{"class":125},[112,30594,10289],{"class":163},[112,30596,6944],{"class":129},[112,30598,4832],{"class":163},[112,30600,2124],{"class":129},[112,30602,30603],{"class":163},"Response",[112,30605,30606],{"class":129},"> ",[112,30608,229],{"class":125},[112,30610,1294],{"class":129},[112,30612,30613,30615,30617,30619,30621,30623,30625,30627,30629,30631,30634,30636,30639],{"class":114,"line":170},[112,30614,1974],{"class":125},[112,30616,9013],{"class":156},[112,30618,160],{"class":125},[112,30620,17976],{"class":163},[112,30622,14088],{"class":129},[112,30624,17981],{"class":163},[112,30626,1261],{"class":129},[112,30628,3344],{"class":163},[112,30630,425],{"class":129},[112,30632,30633],{"class":156},"API_END_POINTS",[112,30635,2124],{"class":129},[112,30637,30638],{"class":156},"USER",[112,30640,431],{"class":129},[112,30642,30643],{"class":114,"line":182},[112,30644,147],{"emptyLinePlaceholder":146},[112,30646,30647,30649],{"class":114,"line":193},[112,30648,3367],{"class":125},[112,30650,30651],{"class":129}," (account) {\n",[112,30653,30654,30657,30659,30661,30664,30666,30669,30671,30674,30676,30678,30680,30682,30684,30686,30688,30690],{"class":114,"line":205},[112,30655,30656],{"class":129},"    req.",[112,30658,3842],{"class":163},[112,30660,425],{"class":129},[112,30662,30663],{"class":136},"'Authorization'",[112,30665,447],{"class":129},[112,30667,30668],{"class":136},"`Bearer ${",[112,30670,5015],{"class":125},[112,30672,30673],{"class":163}," getJwtToken",[112,30675,425],{"class":136},[112,30677,4832],{"class":129},[112,30679,447],{"class":136},[112,30681,849],{"class":129},[112,30683,447],{"class":136},[112,30685,30583],{"class":129},[112,30687,10186],{"class":136},[112,30689,592],{"class":136},[112,30691,431],{"class":129},[112,30693,30694],{"class":114,"line":241},[112,30695,3232],{"class":129},[112,30697,30698],{"class":114,"line":247},[112,30699,147],{"emptyLinePlaceholder":146},[112,30701,30702,30704,30706],{"class":114,"line":351},[112,30703,2048],{"class":125},[112,30705,419],{"class":125},[112,30707,30708],{"class":129}," req\n",[112,30710,30711],{"class":114,"line":361},[112,30712,1452],{"class":129},[112,30714,30715],{"class":114,"line":367},[112,30716,147],{"emptyLinePlaceholder":146},[112,30718,30719],{"class":114,"line":373},[112,30720,30557],{"class":118},[112,30722,30723],{"class":114,"line":379},[112,30724,30725],{"class":118}," * ユーザーを取得\n",[112,30727,30728],{"class":114,"line":1302},[112,30729,30567],{"class":118},[112,30731,30732,30734,30737,30739,30741],{"class":114,"line":1502},[112,30733,153],{"class":125},[112,30735,30736],{"class":163}," show",[112,30738,160],{"class":125},[112,30740,1955],{"class":125},[112,30742,30743],{"class":129}," (\n",[112,30745,30746,30749,30751,30753],{"class":114,"line":1507},[112,30747,30748],{"class":222},"  id",[112,30750,2243],{"class":125},[112,30752,17353],{"class":156},[112,30754,179],{"class":129},[112,30756,30757,30760,30762],{"class":114,"line":1512},[112,30758,30759],{"class":222},"  account",[112,30761,10023],{"class":125},[112,30763,30764],{"class":163}," E2eLoginData\n",[112,30766,30767,30769,30771,30773,30775,30777,30779,30781,30783,30785],{"class":114,"line":1518},[112,30768,10186],{"class":129},[112,30770,2243],{"class":125},[112,30772,10289],{"class":163},[112,30774,6944],{"class":129},[112,30776,4832],{"class":163},[112,30778,2124],{"class":129},[112,30780,30603],{"class":163},[112,30782,30606],{"class":129},[112,30784,229],{"class":125},[112,30786,1294],{"class":129},[112,30788,30789,30791,30793,30795,30797,30799,30801,30803,30805,30807,30809,30811,30813,30815,30818,30820,30822],{"class":114,"line":1524},[112,30790,1974],{"class":125},[112,30792,9013],{"class":156},[112,30794,160],{"class":125},[112,30796,17976],{"class":163},[112,30798,14088],{"class":129},[112,30800,17981],{"class":163},[112,30802,1261],{"class":129},[112,30804,3344],{"class":163},[112,30806,425],{"class":129},[112,30808,13618],{"class":136},[112,30810,30633],{"class":156},[112,30812,2124],{"class":136},[112,30814,30638],{"class":156},[112,30816,30817],{"class":136},"}\u002F${",[112,30819,17348],{"class":129},[112,30821,592],{"class":136},[112,30823,431],{"class":129},[112,30825,30826],{"class":114,"line":1530},[112,30827,147],{"emptyLinePlaceholder":146},[112,30829,30830,30832],{"class":114,"line":1536},[112,30831,3367],{"class":125},[112,30833,30651],{"class":129},[112,30835,30836,30838,30840,30842,30844,30846,30848,30850,30852,30854,30856,30858,30860,30862,30864,30866,30868],{"class":114,"line":1542},[112,30837,30656],{"class":129},[112,30839,3842],{"class":163},[112,30841,425],{"class":129},[112,30843,30663],{"class":136},[112,30845,447],{"class":129},[112,30847,30668],{"class":136},[112,30849,5015],{"class":125},[112,30851,30673],{"class":163},[112,30853,425],{"class":136},[112,30855,4832],{"class":129},[112,30857,447],{"class":136},[112,30859,849],{"class":129},[112,30861,447],{"class":136},[112,30863,30583],{"class":129},[112,30865,10186],{"class":136},[112,30867,592],{"class":136},[112,30869,431],{"class":129},[112,30871,30872],{"class":114,"line":2402},[112,30873,3232],{"class":129},[112,30875,30876],{"class":114,"line":2407},[112,30877,147],{"emptyLinePlaceholder":146},[112,30879,30880,30882,30884],{"class":114,"line":2413},[112,30881,2048],{"class":125},[112,30883,419],{"class":125},[112,30885,30708],{"class":129},[112,30887,30888],{"class":114,"line":2446},[112,30889,1452],{"class":129},[112,30891,30892],{"class":114,"line":2451},[112,30893,147],{"emptyLinePlaceholder":146},[112,30895,30896],{"class":114,"line":2464},[112,30897,30557],{"class":118},[112,30899,30900],{"class":114,"line":2481},[112,30901,30902],{"class":118}," * ユーザーを作成\n",[112,30904,30905],{"class":114,"line":2486},[112,30906,30567],{"class":118},[112,30908,30909,30911,30913,30915,30917],{"class":114,"line":3229},[112,30910,153],{"class":125},[112,30912,6835],{"class":163},[112,30914,160],{"class":125},[112,30916,1955],{"class":125},[112,30918,30743],{"class":129},[112,30920,30921,30924,30926,30928],{"class":114,"line":3235},[112,30922,30923],{"class":222},"  dto",[112,30925,2243],{"class":125},[112,30927,10957],{"class":163},[112,30929,179],{"class":129},[112,30931,30932,30934,30936],{"class":114,"line":3657},[112,30933,30759],{"class":222},[112,30935,10023],{"class":125},[112,30937,30764],{"class":163},[112,30939,30940,30942,30944,30946,30948,30950,30952,30954,30956,30958],{"class":114,"line":3662},[112,30941,10186],{"class":129},[112,30943,2243],{"class":125},[112,30945,10289],{"class":163},[112,30947,6944],{"class":129},[112,30949,4832],{"class":163},[112,30951,2124],{"class":129},[112,30953,30603],{"class":163},[112,30955,30606],{"class":129},[112,30957,229],{"class":125},[112,30959,1294],{"class":129},[112,30961,30962,30964,30966,30968,30970,30972,30974],{"class":114,"line":3667},[112,30963,1974],{"class":125},[112,30965,9013],{"class":156},[112,30967,160],{"class":125},[112,30969,17976],{"class":163},[112,30971,14088],{"class":129},[112,30973,17981],{"class":163},[112,30975,17821],{"class":129},[112,30977,30978,30980,30982,30984,30986,30988,30990],{"class":114,"line":3680},[112,30979,4861],{"class":129},[112,30981,570],{"class":163},[112,30983,425],{"class":129},[112,30985,30633],{"class":156},[112,30987,2124],{"class":129},[112,30989,30638],{"class":156},[112,30991,431],{"class":129},[112,30993,30994,30996,30998,31000,31002,31004,31006],{"class":114,"line":3692},[112,30995,4861],{"class":129},[112,30997,3842],{"class":163},[112,30999,425],{"class":129},[112,31001,18008],{"class":136},[112,31003,447],{"class":129},[112,31005,5264],{"class":136},[112,31007,431],{"class":129},[112,31009,31010,31012,31014],{"class":114,"line":3716},[112,31011,4861],{"class":129},[112,31013,18021],{"class":163},[112,31015,11121],{"class":129},[112,31017,31018],{"class":114,"line":3737},[112,31019,147],{"emptyLinePlaceholder":146},[112,31021,31022,31024],{"class":114,"line":3742},[112,31023,3367],{"class":125},[112,31025,30651],{"class":129},[112,31027,31028,31030,31032,31034,31036,31038,31040,31042,31044,31046,31048,31050,31052,31054,31056,31058,31060],{"class":114,"line":3747},[112,31029,30656],{"class":129},[112,31031,3842],{"class":163},[112,31033,425],{"class":129},[112,31035,30663],{"class":136},[112,31037,447],{"class":129},[112,31039,30668],{"class":136},[112,31041,5015],{"class":125},[112,31043,30673],{"class":163},[112,31045,425],{"class":136},[112,31047,4832],{"class":129},[112,31049,447],{"class":136},[112,31051,849],{"class":129},[112,31053,447],{"class":136},[112,31055,30583],{"class":129},[112,31057,10186],{"class":136},[112,31059,592],{"class":136},[112,31061,431],{"class":129},[112,31063,31064],{"class":114,"line":3752},[112,31065,3232],{"class":129},[112,31067,31068],{"class":114,"line":3758},[112,31069,147],{"emptyLinePlaceholder":146},[112,31071,31072,31074,31076],{"class":114,"line":3778},[112,31073,2048],{"class":125},[112,31075,419],{"class":125},[112,31077,30708],{"class":129},[112,31079,31080],{"class":114,"line":3787},[112,31081,1452],{"class":129},[83,31083,31084],{"id":31084},"テストケース",[103,31086,31088],{"className":105,"code":31087,"language":107,"meta":108,"style":108},"describe('ユーザー一覧取得', () => {\n  it('認証済みユーザーがユーザー一覧を取得できる', async () => {\n    const res = await index(LOGIN_DATA.SERVICE_ADMIN)\n\n    expect(res.status).toEqual(HTTP_STATUS_CODES.OK)\n    expect(res.body).toEqual(INDEX_USERS)\n  })\n})\n\ndescribe('ユーザー詳細取得', () => {\n  it('認証済みユーザーがユーザー詳細を取得できる', async () => {\n    const users = (await index(LOGIN_DATA.SERVICE_ADMIN)).body\n    const id = users[0].id\n\n    const res = await show(id, LOGIN_DATA.SERVICE_ADMIN)\n\n    expect(res.status).toEqual(HTTP_STATUS_CODES.OK)\n    expect(res.body).toEqual(SHOW_USER_DATA)\n  })\n})\n\ndescribe('ユーザー作成', () => {\n  it('認証済みユーザーがユーザーを作成できる', async () => {\n    const body: CreateUserDto = {\n      name: '新規ユーザー',\n      password: 'password',\n      password_confirm: 'password',\n    }\n\n    const res = await create(body, LOGIN_DATA.SERVICE_ADMIN)\n\n    expect(res.status).toEqual(HTTP_STATUS_CODES.CREATED)\n    expect(res.body.message).toEqual(RESPONSE_MESSAGES.USER_CREATED)\n  })\n})\n",[90,31089,31090,31105,31124,31148,31152,31172,31188,31192,31196,31200,31215,31234,31259,31275,31279,31302,31306,31324,31339,31343,31347,31351,31366,31385,31399,31409,31418,31427,31431,31435,31458,31462,31481,31501,31505],{"__ignoreMap":108},[112,31091,31092,31094,31096,31099,31101,31103],{"class":114,"line":115},[112,31093,12401],{"class":163},[112,31095,425],{"class":129},[112,31097,31098],{"class":136},"'ユーザー一覧取得'",[112,31100,595],{"class":129},[112,31102,229],{"class":125},[112,31104,1294],{"class":129},[112,31106,31107,31109,31111,31114,31116,31118,31120,31122],{"class":114,"line":122},[112,31108,12654],{"class":163},[112,31110,425],{"class":129},[112,31112,31113],{"class":136},"'認証済みユーザーがユーザー一覧を取得できる'",[112,31115,447],{"class":129},[112,31117,3305],{"class":125},[112,31119,3802],{"class":129},[112,31121,229],{"class":125},[112,31123,1294],{"class":129},[112,31125,31126,31128,31130,31132,31134,31136,31138,31141,31143,31146],{"class":114,"line":143},[112,31127,3472],{"class":125},[112,31129,17969],{"class":156},[112,31131,160],{"class":125},[112,31133,419],{"class":125},[112,31135,30574],{"class":163},[112,31137,425],{"class":129},[112,31139,31140],{"class":156},"LOGIN_DATA",[112,31142,2124],{"class":129},[112,31144,31145],{"class":156},"SERVICE_ADMIN",[112,31147,431],{"class":129},[112,31149,31150],{"class":114,"line":150},[112,31151,147],{"emptyLinePlaceholder":146},[112,31153,31154,31156,31158,31160,31162,31165,31167,31170],{"class":114,"line":170},[112,31155,12670],{"class":163},[112,31157,18036],{"class":129},[112,31159,18039],{"class":163},[112,31161,425],{"class":129},[112,31163,31164],{"class":156},"HTTP_STATUS_CODES",[112,31166,2124],{"class":129},[112,31168,31169],{"class":156},"OK",[112,31171,431],{"class":129},[112,31173,31174,31176,31179,31181,31183,31186],{"class":114,"line":182},[112,31175,12670],{"class":163},[112,31177,31178],{"class":129},"(res.body).",[112,31180,18039],{"class":163},[112,31182,425],{"class":129},[112,31184,31185],{"class":156},"INDEX_USERS",[112,31187,431],{"class":129},[112,31189,31190],{"class":114,"line":193},[112,31191,8004],{"class":129},[112,31193,31194],{"class":114,"line":205},[112,31195,8436],{"class":129},[112,31197,31198],{"class":114,"line":241},[112,31199,147],{"emptyLinePlaceholder":146},[112,31201,31202,31204,31206,31209,31211,31213],{"class":114,"line":247},[112,31203,12401],{"class":163},[112,31205,425],{"class":129},[112,31207,31208],{"class":136},"'ユーザー詳細取得'",[112,31210,595],{"class":129},[112,31212,229],{"class":125},[112,31214,1294],{"class":129},[112,31216,31217,31219,31221,31224,31226,31228,31230,31232],{"class":114,"line":351},[112,31218,12654],{"class":163},[112,31220,425],{"class":129},[112,31222,31223],{"class":136},"'認証済みユーザーがユーザー詳細を取得できる'",[112,31225,447],{"class":129},[112,31227,3305],{"class":125},[112,31229,3802],{"class":129},[112,31231,229],{"class":125},[112,31233,1294],{"class":129},[112,31235,31236,31238,31240,31242,31244,31246,31248,31250,31252,31254,31256],{"class":114,"line":361},[112,31237,3472],{"class":125},[112,31239,24024],{"class":156},[112,31241,160],{"class":125},[112,31243,3945],{"class":129},[112,31245,5015],{"class":125},[112,31247,30574],{"class":163},[112,31249,425],{"class":129},[112,31251,31140],{"class":156},[112,31253,2124],{"class":129},[112,31255,31145],{"class":156},[112,31257,31258],{"class":129},")).body\n",[112,31260,31261,31263,31265,31267,31270,31272],{"class":114,"line":367},[112,31262,3472],{"class":125},[112,31264,16166],{"class":156},[112,31266,160],{"class":125},[112,31268,31269],{"class":129}," users[",[112,31271,5392],{"class":156},[112,31273,31274],{"class":129},"].id\n",[112,31276,31277],{"class":114,"line":373},[112,31278,147],{"emptyLinePlaceholder":146},[112,31280,31281,31283,31285,31287,31289,31291,31294,31296,31298,31300],{"class":114,"line":379},[112,31282,3472],{"class":125},[112,31284,17969],{"class":156},[112,31286,160],{"class":125},[112,31288,419],{"class":125},[112,31290,30736],{"class":163},[112,31292,31293],{"class":129},"(id, ",[112,31295,31140],{"class":156},[112,31297,2124],{"class":129},[112,31299,31145],{"class":156},[112,31301,431],{"class":129},[112,31303,31304],{"class":114,"line":1302},[112,31305,147],{"emptyLinePlaceholder":146},[112,31307,31308,31310,31312,31314,31316,31318,31320,31322],{"class":114,"line":1502},[112,31309,12670],{"class":163},[112,31311,18036],{"class":129},[112,31313,18039],{"class":163},[112,31315,425],{"class":129},[112,31317,31164],{"class":156},[112,31319,2124],{"class":129},[112,31321,31169],{"class":156},[112,31323,431],{"class":129},[112,31325,31326,31328,31330,31332,31334,31337],{"class":114,"line":1507},[112,31327,12670],{"class":163},[112,31329,31178],{"class":129},[112,31331,18039],{"class":163},[112,31333,425],{"class":129},[112,31335,31336],{"class":156},"SHOW_USER_DATA",[112,31338,431],{"class":129},[112,31340,31341],{"class":114,"line":1512},[112,31342,8004],{"class":129},[112,31344,31345],{"class":114,"line":1518},[112,31346,8436],{"class":129},[112,31348,31349],{"class":114,"line":1524},[112,31350,147],{"emptyLinePlaceholder":146},[112,31352,31353,31355,31357,31360,31362,31364],{"class":114,"line":1530},[112,31354,12401],{"class":163},[112,31356,425],{"class":129},[112,31358,31359],{"class":136},"'ユーザー作成'",[112,31361,595],{"class":129},[112,31363,229],{"class":125},[112,31365,1294],{"class":129},[112,31367,31368,31370,31372,31375,31377,31379,31381,31383],{"class":114,"line":1536},[112,31369,12654],{"class":163},[112,31371,425],{"class":129},[112,31373,31374],{"class":136},"'認証済みユーザーがユーザーを作成できる'",[112,31376,447],{"class":129},[112,31378,3305],{"class":125},[112,31380,3802],{"class":129},[112,31382,229],{"class":125},[112,31384,1294],{"class":129},[112,31386,31387,31389,31391,31393,31395,31397],{"class":114,"line":1542},[112,31388,3472],{"class":125},[112,31390,17916],{"class":156},[112,31392,2243],{"class":125},[112,31394,10957],{"class":163},[112,31396,160],{"class":125},[112,31398,1294],{"class":129},[112,31400,31401,31404,31407],{"class":114,"line":2402},[112,31402,31403],{"class":129},"      name: ",[112,31405,31406],{"class":136},"'新規ユーザー'",[112,31408,179],{"class":129},[112,31410,31411,31413,31416],{"class":114,"line":2407},[112,31412,17253],{"class":129},[112,31414,31415],{"class":136},"'password'",[112,31417,179],{"class":129},[112,31419,31420,31423,31425],{"class":114,"line":2413},[112,31421,31422],{"class":129},"      password_confirm: ",[112,31424,31415],{"class":136},[112,31426,179],{"class":129},[112,31428,31429],{"class":114,"line":2446},[112,31430,3118],{"class":129},[112,31432,31433],{"class":114,"line":2451},[112,31434,147],{"emptyLinePlaceholder":146},[112,31436,31437,31439,31441,31443,31445,31447,31450,31452,31454,31456],{"class":114,"line":2464},[112,31438,3472],{"class":125},[112,31440,17969],{"class":156},[112,31442,160],{"class":125},[112,31444,419],{"class":125},[112,31446,6835],{"class":163},[112,31448,31449],{"class":129},"(body, ",[112,31451,31140],{"class":156},[112,31453,2124],{"class":129},[112,31455,31145],{"class":156},[112,31457,431],{"class":129},[112,31459,31460],{"class":114,"line":2481},[112,31461,147],{"emptyLinePlaceholder":146},[112,31463,31464,31466,31468,31470,31472,31474,31476,31479],{"class":114,"line":2486},[112,31465,12670],{"class":163},[112,31467,18036],{"class":129},[112,31469,18039],{"class":163},[112,31471,425],{"class":129},[112,31473,31164],{"class":156},[112,31475,2124],{"class":129},[112,31477,31478],{"class":156},"CREATED",[112,31480,431],{"class":129},[112,31482,31483,31485,31487,31489,31491,31494,31496,31499],{"class":114,"line":3229},[112,31484,12670],{"class":163},[112,31486,18053],{"class":129},[112,31488,18039],{"class":163},[112,31490,425],{"class":129},[112,31492,31493],{"class":156},"RESPONSE_MESSAGES",[112,31495,2124],{"class":129},[112,31497,31498],{"class":156},"USER_CREATED",[112,31500,431],{"class":129},[112,31502,31503],{"class":114,"line":3235},[112,31504,8004],{"class":129},[112,31506,31507],{"class":114,"line":3657},[112,31508,8436],{"class":129},[11,31510,29335],{"id":29335},[103,31512,31513],{"className":771,"code":18271,"language":773,"meta":108,"style":108},[90,31514,31515],{"__ignoreMap":108},[112,31516,31517,31519,31521],{"class":114,"line":115},[112,31518,6832],{"class":163},[112,31520,6901],{"class":136},[112,31522,18282],{"class":136},[83,31524,29386],{"id":29386},[103,31526,31529],{"className":31527,"code":31528,"language":5951},[5949],"PASS  src\u002Ftest\u002Fe2e\u002Fuser.e2e-spec.ts (33.623 s)\n  ユーザーAPI (E2E)\n    ユーザー一覧取得\n      ✓ 認証済みユーザーがユーザー一覧を取得できる (1748 ms)\n    ユーザー詳細取得\n      ✓ 認証済みユーザーがユーザー詳細を取得できる (1735 ms)\n    ユーザー作成\n      ✓ 認証済みユーザーがユーザーを作成できる (1493 ms)\n",[90,31530,31528],{"__ignoreMap":108},[11,31532,31534],{"id":31533},"cicd環境での実行","CI\u002FCD環境での実行",[15,31536,31537],{},[759,31538,31539],{"href":29618},"docker-compose + GitHub ActionsでCI\u002FCD環境を構築する方法はこちら",[11,31541,919],{"id":919},[15,31543,31544],{},"NestJSとJestを使ったE2Eテスト環境を構築することで、以下のメリットが得られます。",[31,31546,31547,31556,31564],{},[34,31548,31549,9512,31552,31555],{},[27,31550,31551],{},"統合テスト",[90,31553,31554],{},"Test.createTestingModule()","で本番環境と同じDIコンテナを再現",[34,31557,31558,9512,31561,31563],{},[27,31559,31560],{},"独立性",[90,31562,18809],{},"でデータベースをリセットし、テスト間の依存を排除",[34,31565,31566,31569],{},[27,31567,31568],{},"実用性"," supertestでHTTPリクエストをプログラマティックにテスト",[15,31571,31572],{},"TypeORMのsynchronize機能を活用することで、テスト環境のセットアップも自動化できます。",[944,31574,31575],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":108,"searchDepth":122,"depth":122,"links":31577},[31578,31579,31580,31583,31584,31585,31589,31594,31597,31598],{"id":13,"depth":122,"text":13},{"id":29634,"depth":122,"text":29634},{"id":29643,"depth":122,"text":29644,"children":31581},[31582],{"id":15493,"depth":143,"text":15493},{"id":29112,"depth":122,"text":29113},{"id":15456,"depth":122,"text":15456},{"id":30043,"depth":122,"text":30044,"children":31586},[31587,31588],{"id":30050,"depth":143,"text":30050},{"id":30059,"depth":143,"text":30059},{"id":17544,"depth":122,"text":17545,"children":31590},[31591,31592,31593],{"id":30153,"depth":143,"text":30153},{"id":30547,"depth":143,"text":30547},{"id":31084,"depth":143,"text":31084},{"id":29335,"depth":122,"text":29335,"children":31595},[31596],{"id":29386,"depth":143,"text":29386},{"id":31533,"depth":122,"text":31534},{"id":919,"depth":122,"text":919},"NestJSとJestを使ったE2Eテストの実践的な導入方法を解説します。Test.createTestingModuleを使った統合テストの書き方から、TypeORMのsynchronize機能を活用したテスト環境構築まで詳しく紹介します。",{"tags":31601},[18866,18868,12798,29617,26492],{"title":29624,"description":31599},"blog\u002Fjest-nestjs-e2e-testing","MWL6ASP_0aFswwfDyFfvMQF9peZ2fW8-edgICT5ri_w",{"id":31606,"title":31607,"body":31608,"date":29613,"description":34844,"extension":962,"meta":34845,"navigation":146,"path":34848,"seo":34849,"stem":34850,"__hash__":34851},"blog\u002Fblog\u002Ftypeorm-entity-basics.md","TypeORMでEntityを定義する - 基礎から実践まで",{"type":8,"value":31609,"toc":34807},[31610,31614,31617,31622,31636,31640,31644,31746,31749,31832,31835,31838,32129,32133,32302,32305,32458,32461,32465,32703,32707,32984,32988,33248,33251,33255,33258,33421,33425,33659,33663,33793,33796,33800,33803,33906,33910,33918,33921,33924,34109,34113,34116,34343,34347,34525,34528,34532,34539,34612,34616,34619,34656,34660,34663,34692,34696,34703,34735,34737,34740,34776,34779,34781,34804],[11,31611,31613],{"id":31612},"typeormのentityとは","TypeORMのEntityとは",[15,31615,31616],{},"TypeORMのEntityは、データベーステーブルとTypeScriptクラスをマッピングするためのクラスです。",[15,31618,31619],{},[27,31620,31621],{},"Entityの役割",[31,31623,31624,31627,31630,31633],{},[34,31625,31626],{},"データベーステーブルの構造を定義",[34,31628,31629],{},"カラムの型や制約を指定",[34,31631,31632],{},"テーブル間のリレーションを表現",[34,31634,31635],{},"オブジェクト指向のデータ操作を実現",[11,31637,31639],{"id":31638},"基本的なentity定義","基本的なEntity定義",[83,31641,31643],{"id":31642},"最小構成のentity","最小構成のEntity",[103,31645,31647],{"className":105,"code":31646,"language":107,"meta":108,"style":108},"import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'\n\n@Entity('users')\nexport class User {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column()\n  name: string\n\n  @Column()\n  email: string\n}\n",[90,31648,31649,31660,31664,31676,31686,31694,31702,31706,31714,31722,31726,31734,31742],{"__ignoreMap":108},[112,31650,31651,31653,31656,31658],{"class":114,"line":115},[112,31652,126],{"class":125},[112,31654,31655],{"class":129}," { Entity, PrimaryGeneratedColumn, Column } ",[112,31657,133],{"class":125},[112,31659,16078],{"class":136},[112,31661,31662],{"class":114,"line":122},[112,31663,147],{"emptyLinePlaceholder":146},[112,31665,31666,31668,31670,31672,31674],{"class":114,"line":143},[112,31667,9901],{"class":129},[112,31669,16089],{"class":163},[112,31671,425],{"class":129},[112,31673,11037],{"class":136},[112,31675,431],{"class":129},[112,31677,31678,31680,31682,31684],{"class":114,"line":150},[112,31679,288],{"class":125},[112,31681,9931],{"class":125},[112,31683,16104],{"class":163},[112,31685,1294],{"class":129},[112,31687,31688,31690,31692],{"class":114,"line":170},[112,31689,11079],{"class":129},[112,31691,16113],{"class":163},[112,31693,630],{"class":129},[112,31695,31696,31698,31700],{"class":114,"line":182},[112,31697,30748],{"class":222},[112,31699,2243],{"class":125},[112,31701,16171],{"class":156},[112,31703,31704],{"class":114,"line":193},[112,31705,147],{"emptyLinePlaceholder":146},[112,31707,31708,31710,31712],{"class":114,"line":205},[112,31709,11079],{"class":129},[112,31711,16182],{"class":163},[112,31713,630],{"class":129},[112,31715,31716,31718,31720],{"class":114,"line":241},[112,31717,10020],{"class":222},[112,31719,2243],{"class":125},[112,31721,10006],{"class":156},[112,31723,31724],{"class":114,"line":247},[112,31725,147],{"emptyLinePlaceholder":146},[112,31727,31728,31730,31732],{"class":114,"line":351},[112,31729,11079],{"class":129},[112,31731,16182],{"class":163},[112,31733,630],{"class":129},[112,31735,31736,31738,31740],{"class":114,"line":361},[112,31737,10001],{"class":222},[112,31739,2243],{"class":125},[112,31741,10006],{"class":156},[112,31743,31744],{"class":114,"line":367},[112,31745,1452],{"class":129},[83,31747,31748],{"id":31748},"主要なデコレータ",[7735,31750,31751,31760],{},[7738,31752,31753],{},[7741,31754,31755,31758],{},[7744,31756,31757],{},"デコレータ",[7744,31759,8838],{},[7754,31761,31762,31772,31782,31792,31802,31812,31822],{},[7741,31763,31764,31769],{},[7759,31765,31766],{},[90,31767,31768],{},"@Entity()",[7759,31770,31771],{},"クラスがEntityであることを宣言",[7741,31773,31774,31779],{},[7759,31775,31776],{},[90,31777,31778],{},"@PrimaryGeneratedColumn()",[7759,31780,31781],{},"自動採番の主キー",[7741,31783,31784,31789],{},[7759,31785,31786],{},[90,31787,31788],{},"@PrimaryColumn()",[7759,31790,31791],{},"手動設定の主キー",[7741,31793,31794,31799],{},[7759,31795,31796],{},[90,31797,31798],{},"@Column()",[7759,31800,31801],{},"通常のカラム",[7741,31803,31804,31809],{},[7759,31805,31806],{},[90,31807,31808],{},"@CreateDateColumn()",[7759,31810,31811],{},"作成日時（自動設定）",[7741,31813,31814,31819],{},[7759,31815,31816],{},[90,31817,31818],{},"@UpdateDateColumn()",[7759,31820,31821],{},"更新日時（自動更新）",[7741,31823,31824,31829],{},[7759,31825,31826],{},[90,31827,31828],{},"@DeleteDateColumn()",[7759,31830,31831],{},"削除日時（ソフトデリート用）",[11,31833,31834],{"id":31834},"カラム定義の詳細",[83,31836,31837],{"id":31837},"カラムの型指定",[103,31839,31841],{"className":105,"code":31840,"language":107,"meta":108,"style":108},"@Entity()\nexport class Product {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  \u002F\u002F 文字列型\n  @Column('varchar', { length: 255 })\n  name: string\n\n  \u002F\u002F 数値型（整数）\n  @Column('int', { unsigned: true })\n  price: number\n\n  \u002F\u002F 数値型（小数）\n  @Column('decimal', { precision: 10, scale: 2 })\n  weight: number\n\n  \u002F\u002F 真偽値\n  @Column('boolean', { default: true })\n  isActive: boolean\n\n  \u002F\u002F テキスト\n  @Column('text')\n  description: string\n\n  \u002F\u002F 日付\n  @Column('date')\n  publishedAt: Date\n\n  \u002F\u002F JSON\n  @Column('json', { nullable: true })\n  metadata: object\n}\n",[90,31842,31843,31851,31862,31870,31878,31882,31887,31904,31912,31916,31921,31939,31948,31952,31957,31980,31989,31993,31998,32016,32026,32030,32035,32048,32057,32061,32066,32078,32088,32092,32097,32115,32125],{"__ignoreMap":108},[112,31844,31845,31847,31849],{"class":114,"line":115},[112,31846,9901],{"class":129},[112,31848,16089],{"class":163},[112,31850,630],{"class":129},[112,31852,31853,31855,31857,31860],{"class":114,"line":122},[112,31854,288],{"class":125},[112,31856,9931],{"class":125},[112,31858,31859],{"class":163}," Product",[112,31861,1294],{"class":129},[112,31863,31864,31866,31868],{"class":114,"line":143},[112,31865,11079],{"class":129},[112,31867,16113],{"class":163},[112,31869,630],{"class":129},[112,31871,31872,31874,31876],{"class":114,"line":150},[112,31873,30748],{"class":222},[112,31875,2243],{"class":125},[112,31877,16171],{"class":156},[112,31879,31880],{"class":114,"line":170},[112,31881,147],{"emptyLinePlaceholder":146},[112,31883,31884],{"class":114,"line":182},[112,31885,31886],{"class":118},"  \u002F\u002F 文字列型\n",[112,31888,31889,31891,31893,31895,31897,31900,31902],{"class":114,"line":193},[112,31890,11079],{"class":129},[112,31892,16182],{"class":163},[112,31894,425],{"class":129},[112,31896,16188],{"class":136},[112,31898,31899],{"class":129},", { length: ",[112,31901,16194],{"class":156},[112,31903,11585],{"class":129},[112,31905,31906,31908,31910],{"class":114,"line":205},[112,31907,10020],{"class":222},[112,31909,2243],{"class":125},[112,31911,10006],{"class":156},[112,31913,31914],{"class":114,"line":241},[112,31915,147],{"emptyLinePlaceholder":146},[112,31917,31918],{"class":114,"line":247},[112,31919,31920],{"class":118},"  \u002F\u002F 数値型（整数）\n",[112,31922,31923,31925,31927,31929,31932,31935,31937],{"class":114,"line":351},[112,31924,11079],{"class":129},[112,31926,16182],{"class":163},[112,31928,425],{"class":129},[112,31930,31931],{"class":136},"'int'",[112,31933,31934],{"class":129},", { unsigned: ",[112,31936,4345],{"class":156},[112,31938,11585],{"class":129},[112,31940,31941,31944,31946],{"class":114,"line":361},[112,31942,31943],{"class":222},"  price",[112,31945,2243],{"class":125},[112,31947,16171],{"class":156},[112,31949,31950],{"class":114,"line":367},[112,31951,147],{"emptyLinePlaceholder":146},[112,31953,31954],{"class":114,"line":373},[112,31955,31956],{"class":118},"  \u002F\u002F 数値型（小数）\n",[112,31958,31959,31961,31963,31965,31968,31971,31973,31976,31978],{"class":114,"line":379},[112,31960,11079],{"class":129},[112,31962,16182],{"class":163},[112,31964,425],{"class":129},[112,31966,31967],{"class":136},"'decimal'",[112,31969,31970],{"class":129},", { precision: ",[112,31972,12294],{"class":156},[112,31974,31975],{"class":129},", scale: ",[112,31977,19956],{"class":156},[112,31979,11585],{"class":129},[112,31981,31982,31985,31987],{"class":114,"line":1302},[112,31983,31984],{"class":222},"  weight",[112,31986,2243],{"class":125},[112,31988,16171],{"class":156},[112,31990,31991],{"class":114,"line":1502},[112,31992,147],{"emptyLinePlaceholder":146},[112,31994,31995],{"class":114,"line":1507},[112,31996,31997],{"class":118},"  \u002F\u002F 真偽値\n",[112,31999,32000,32002,32004,32006,32009,32012,32014],{"class":114,"line":1512},[112,32001,11079],{"class":129},[112,32003,16182],{"class":163},[112,32005,425],{"class":129},[112,32007,32008],{"class":136},"'boolean'",[112,32010,32011],{"class":129},", { default: ",[112,32013,4345],{"class":156},[112,32015,11585],{"class":129},[112,32017,32018,32021,32023],{"class":114,"line":1518},[112,32019,32020],{"class":222},"  isActive",[112,32022,2243],{"class":125},[112,32024,32025],{"class":156}," boolean\n",[112,32027,32028],{"class":114,"line":1524},[112,32029,147],{"emptyLinePlaceholder":146},[112,32031,32032],{"class":114,"line":1530},[112,32033,32034],{"class":118},"  \u002F\u002F テキスト\n",[112,32036,32037,32039,32041,32043,32046],{"class":114,"line":1536},[112,32038,11079],{"class":129},[112,32040,16182],{"class":163},[112,32042,425],{"class":129},[112,32044,32045],{"class":136},"'text'",[112,32047,431],{"class":129},[112,32049,32050,32053,32055],{"class":114,"line":1542},[112,32051,32052],{"class":222},"  description",[112,32054,2243],{"class":125},[112,32056,10006],{"class":156},[112,32058,32059],{"class":114,"line":2402},[112,32060,147],{"emptyLinePlaceholder":146},[112,32062,32063],{"class":114,"line":2407},[112,32064,32065],{"class":118},"  \u002F\u002F 日付\n",[112,32067,32068,32070,32072,32074,32076],{"class":114,"line":2413},[112,32069,11079],{"class":129},[112,32071,16182],{"class":163},[112,32073,425],{"class":129},[112,32075,444],{"class":136},[112,32077,431],{"class":129},[112,32079,32080,32083,32085],{"class":114,"line":2446},[112,32081,32082],{"class":222},"  publishedAt",[112,32084,2243],{"class":125},[112,32086,32087],{"class":163}," Date\n",[112,32089,32090],{"class":114,"line":2451},[112,32091,147],{"emptyLinePlaceholder":146},[112,32093,32094],{"class":114,"line":2464},[112,32095,32096],{"class":118},"  \u002F\u002F JSON\n",[112,32098,32099,32101,32103,32105,32108,32111,32113],{"class":114,"line":2481},[112,32100,11079],{"class":129},[112,32102,16182],{"class":163},[112,32104,425],{"class":129},[112,32106,32107],{"class":136},"'json'",[112,32109,32110],{"class":129},", { nullable: ",[112,32112,4345],{"class":156},[112,32114,11585],{"class":129},[112,32116,32117,32120,32122],{"class":114,"line":2486},[112,32118,32119],{"class":222},"  metadata",[112,32121,2243],{"class":125},[112,32123,32124],{"class":156}," object\n",[112,32126,32127],{"class":114,"line":3229},[112,32128,1452],{"class":129},[83,32130,32132],{"id":32131},"nullを許容するカラム","NULLを許容するカラム",[103,32134,32136],{"className":105,"code":32135,"language":107,"meta":108,"style":108},"@Entity()\nexport class User {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column()\n  name: string\n\n  \u002F\u002F NULLを許容\n  @Column({ nullable: true })\n  phoneNumber: string | null\n\n  \u002F\u002F デフォルト値を設定\n  @Column({ default: 'user' })\n  role: string\n\n  \u002F\u002F NULLの初期値を明示（TypeScript側の型安全性のため）\n  @Column({ nullable: true })\n  bio: string | null = null\n}\n",[90,32137,32138,32146,32156,32164,32172,32176,32184,32192,32196,32201,32213,32228,32232,32237,32251,32260,32264,32269,32281,32298],{"__ignoreMap":108},[112,32139,32140,32142,32144],{"class":114,"line":115},[112,32141,9901],{"class":129},[112,32143,16089],{"class":163},[112,32145,630],{"class":129},[112,32147,32148,32150,32152,32154],{"class":114,"line":122},[112,32149,288],{"class":125},[112,32151,9931],{"class":125},[112,32153,16104],{"class":163},[112,32155,1294],{"class":129},[112,32157,32158,32160,32162],{"class":114,"line":143},[112,32159,11079],{"class":129},[112,32161,16113],{"class":163},[112,32163,630],{"class":129},[112,32165,32166,32168,32170],{"class":114,"line":150},[112,32167,30748],{"class":222},[112,32169,2243],{"class":125},[112,32171,16171],{"class":156},[112,32173,32174],{"class":114,"line":170},[112,32175,147],{"emptyLinePlaceholder":146},[112,32177,32178,32180,32182],{"class":114,"line":182},[112,32179,11079],{"class":129},[112,32181,16182],{"class":163},[112,32183,630],{"class":129},[112,32185,32186,32188,32190],{"class":114,"line":193},[112,32187,10020],{"class":222},[112,32189,2243],{"class":125},[112,32191,10006],{"class":156},[112,32193,32194],{"class":114,"line":205},[112,32195,147],{"emptyLinePlaceholder":146},[112,32197,32198],{"class":114,"line":241},[112,32199,32200],{"class":118},"  \u002F\u002F NULLを許容\n",[112,32202,32203,32205,32207,32209,32211],{"class":114,"line":247},[112,32204,11079],{"class":129},[112,32206,16182],{"class":163},[112,32208,11580],{"class":129},[112,32210,4345],{"class":156},[112,32212,11585],{"class":129},[112,32214,32215,32218,32220,32222,32225],{"class":114,"line":351},[112,32216,32217],{"class":222},"  phoneNumber",[112,32219,2243],{"class":125},[112,32221,10478],{"class":156},[112,32223,32224],{"class":125}," |",[112,32226,32227],{"class":156}," null\n",[112,32229,32230],{"class":114,"line":361},[112,32231,147],{"emptyLinePlaceholder":146},[112,32233,32234],{"class":114,"line":367},[112,32235,32236],{"class":118},"  \u002F\u002F デフォルト値を設定\n",[112,32238,32239,32241,32243,32246,32249],{"class":114,"line":373},[112,32240,11079],{"class":129},[112,32242,16182],{"class":163},[112,32244,32245],{"class":129},"({ default: ",[112,32247,32248],{"class":136},"'user'",[112,32250,11585],{"class":129},[112,32252,32253,32256,32258],{"class":114,"line":379},[112,32254,32255],{"class":222},"  role",[112,32257,2243],{"class":125},[112,32259,10006],{"class":156},[112,32261,32262],{"class":114,"line":1302},[112,32263,147],{"emptyLinePlaceholder":146},[112,32265,32266],{"class":114,"line":1502},[112,32267,32268],{"class":118},"  \u002F\u002F NULLの初期値を明示（TypeScript側の型安全性のため）\n",[112,32270,32271,32273,32275,32277,32279],{"class":114,"line":1507},[112,32272,11079],{"class":129},[112,32274,16182],{"class":163},[112,32276,11580],{"class":129},[112,32278,4345],{"class":156},[112,32280,11585],{"class":129},[112,32282,32283,32286,32288,32290,32292,32294,32296],{"class":114,"line":1512},[112,32284,32285],{"class":222},"  bio",[112,32287,2243],{"class":125},[112,32289,10478],{"class":156},[112,32291,32224],{"class":125},[112,32293,3425],{"class":156},[112,32295,160],{"class":125},[112,32297,32227],{"class":156},[112,32299,32300],{"class":114,"line":1518},[112,32301,1452],{"class":129},[83,32303,32304],{"id":32304},"タイムスタンプカラム",[103,32306,32308],{"className":105,"code":32307,"language":107,"meta":108,"style":108},"@Entity()\nexport class Post {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column()\n  title: string\n\n  \u002F\u002F 作成日時（INSERT時に自動設定）\n  @CreateDateColumn()\n  readonly createdAt: Date\n\n  \u002F\u002F 更新日時（UPDATE時に自動更新）\n  @UpdateDateColumn()\n  readonly updatedAt: Date\n\n  \u002F\u002F 削除日時（ソフトデリート用）\n  @DeleteDateColumn()\n  readonly deletedAt: Date | null\n}\n",[90,32309,32310,32318,32329,32337,32345,32349,32357,32366,32370,32375,32383,32394,32398,32403,32411,32422,32426,32431,32439,32454],{"__ignoreMap":108},[112,32311,32312,32314,32316],{"class":114,"line":115},[112,32313,9901],{"class":129},[112,32315,16089],{"class":163},[112,32317,630],{"class":129},[112,32319,32320,32322,32324,32327],{"class":114,"line":122},[112,32321,288],{"class":125},[112,32323,9931],{"class":125},[112,32325,32326],{"class":163}," Post",[112,32328,1294],{"class":129},[112,32330,32331,32333,32335],{"class":114,"line":143},[112,32332,11079],{"class":129},[112,32334,16113],{"class":163},[112,32336,630],{"class":129},[112,32338,32339,32341,32343],{"class":114,"line":150},[112,32340,30748],{"class":222},[112,32342,2243],{"class":125},[112,32344,16171],{"class":156},[112,32346,32347],{"class":114,"line":170},[112,32348,147],{"emptyLinePlaceholder":146},[112,32350,32351,32353,32355],{"class":114,"line":182},[112,32352,11079],{"class":129},[112,32354,16182],{"class":163},[112,32356,630],{"class":129},[112,32358,32359,32362,32364],{"class":114,"line":193},[112,32360,32361],{"class":222},"  title",[112,32363,2243],{"class":125},[112,32365,10006],{"class":156},[112,32367,32368],{"class":114,"line":205},[112,32369,147],{"emptyLinePlaceholder":146},[112,32371,32372],{"class":114,"line":241},[112,32373,32374],{"class":118},"  \u002F\u002F 作成日時（INSERT時に自動設定）\n",[112,32376,32377,32379,32381],{"class":114,"line":247},[112,32378,11079],{"class":129},[112,32380,16292],{"class":163},[112,32382,630],{"class":129},[112,32384,32385,32387,32390,32392],{"class":114,"line":351},[112,32386,16163],{"class":125},[112,32388,32389],{"class":222}," createdAt",[112,32391,2243],{"class":125},[112,32393,32087],{"class":163},[112,32395,32396],{"class":114,"line":361},[112,32397,147],{"emptyLinePlaceholder":146},[112,32399,32400],{"class":114,"line":367},[112,32401,32402],{"class":118},"  \u002F\u002F 更新日時（UPDATE時に自動更新）\n",[112,32404,32405,32407,32409],{"class":114,"line":373},[112,32406,11079],{"class":129},[112,32408,16323],{"class":163},[112,32410,630],{"class":129},[112,32412,32413,32415,32418,32420],{"class":114,"line":379},[112,32414,16163],{"class":125},[112,32416,32417],{"class":222}," updatedAt",[112,32419,2243],{"class":125},[112,32421,32087],{"class":163},[112,32423,32424],{"class":114,"line":1302},[112,32425,147],{"emptyLinePlaceholder":146},[112,32427,32428],{"class":114,"line":1502},[112,32429,32430],{"class":118},"  \u002F\u002F 削除日時（ソフトデリート用）\n",[112,32432,32433,32435,32437],{"class":114,"line":1507},[112,32434,11079],{"class":129},[112,32436,16352],{"class":163},[112,32438,630],{"class":129},[112,32440,32441,32443,32446,32448,32450,32452],{"class":114,"line":1512},[112,32442,16163],{"class":125},[112,32444,32445],{"class":222}," deletedAt",[112,32447,2243],{"class":125},[112,32449,235],{"class":163},[112,32451,32224],{"class":125},[112,32453,32227],{"class":156},[112,32455,32456],{"class":114,"line":1518},[112,32457,1452],{"class":129},[11,32459,32460],{"id":32460},"リレーションの定義",[83,32462,32464],{"id":32463},"one-to-one1対1","One-to-One（1対1）",[103,32466,32468],{"className":105,"code":32467,"language":107,"meta":108,"style":108},"\u002F\u002F User Entity\n@Entity()\nexport class User {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column()\n  name: string\n\n  @OneToOne(() => Profile, (profile) => profile.user, {\n    cascade: true, \u002F\u002F Userを保存時にProfileも自動保存\n  })\n  @JoinColumn() \u002F\u002F 外部キーを持つ側に付ける\n  profile: Profile\n}\n\n\u002F\u002F Profile Entity\n@Entity()\nexport class Profile {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column()\n  bio: string\n\n  @OneToOne(() => User, (user) => user.profile)\n  user: User\n}\n",[90,32469,32470,32475,32483,32493,32501,32509,32513,32521,32529,32533,32556,32568,32572,32584,32594,32598,32602,32607,32615,32626,32634,32642,32646,32654,32662,32666,32689,32699],{"__ignoreMap":108},[112,32471,32472],{"class":114,"line":115},[112,32473,32474],{"class":118},"\u002F\u002F User Entity\n",[112,32476,32477,32479,32481],{"class":114,"line":122},[112,32478,9901],{"class":129},[112,32480,16089],{"class":163},[112,32482,630],{"class":129},[112,32484,32485,32487,32489,32491],{"class":114,"line":143},[112,32486,288],{"class":125},[112,32488,9931],{"class":125},[112,32490,16104],{"class":163},[112,32492,1294],{"class":129},[112,32494,32495,32497,32499],{"class":114,"line":150},[112,32496,11079],{"class":129},[112,32498,16113],{"class":163},[112,32500,630],{"class":129},[112,32502,32503,32505,32507],{"class":114,"line":170},[112,32504,30748],{"class":222},[112,32506,2243],{"class":125},[112,32508,16171],{"class":156},[112,32510,32511],{"class":114,"line":182},[112,32512,147],{"emptyLinePlaceholder":146},[112,32514,32515,32517,32519],{"class":114,"line":193},[112,32516,11079],{"class":129},[112,32518,16182],{"class":163},[112,32520,630],{"class":129},[112,32522,32523,32525,32527],{"class":114,"line":205},[112,32524,10020],{"class":222},[112,32526,2243],{"class":125},[112,32528,10006],{"class":156},[112,32530,32531],{"class":114,"line":241},[112,32532,147],{"emptyLinePlaceholder":146},[112,32534,32535,32537,32540,32542,32544,32547,32549,32551,32553],{"class":114,"line":247},[112,32536,11079],{"class":129},[112,32538,32539],{"class":163},"OneToOne",[112,32541,11758],{"class":129},[112,32543,229],{"class":125},[112,32545,32546],{"class":129}," Profile, (",[112,32548,7142],{"class":222},[112,32550,226],{"class":129},[112,32552,229],{"class":125},[112,32554,32555],{"class":129}," profile.user, {\n",[112,32557,32558,32561,32563,32565],{"class":114,"line":351},[112,32559,32560],{"class":129},"    cascade: ",[112,32562,4345],{"class":156},[112,32564,447],{"class":129},[112,32566,32567],{"class":118},"\u002F\u002F Userを保存時にProfileも自動保存\n",[112,32569,32570],{"class":114,"line":361},[112,32571,8004],{"class":129},[112,32573,32574,32576,32579,32581],{"class":114,"line":367},[112,32575,11079],{"class":129},[112,32577,32578],{"class":163},"JoinColumn",[112,32580,1392],{"class":129},[112,32582,32583],{"class":118},"\u002F\u002F 外部キーを持つ側に付ける\n",[112,32585,32586,32589,32591],{"class":114,"line":373},[112,32587,32588],{"class":222},"  profile",[112,32590,2243],{"class":125},[112,32592,32593],{"class":163}," Profile\n",[112,32595,32596],{"class":114,"line":379},[112,32597,1452],{"class":129},[112,32599,32600],{"class":114,"line":1302},[112,32601,147],{"emptyLinePlaceholder":146},[112,32603,32604],{"class":114,"line":1502},[112,32605,32606],{"class":118},"\u002F\u002F Profile Entity\n",[112,32608,32609,32611,32613],{"class":114,"line":1507},[112,32610,9901],{"class":129},[112,32612,16089],{"class":163},[112,32614,630],{"class":129},[112,32616,32617,32619,32621,32624],{"class":114,"line":1512},[112,32618,288],{"class":125},[112,32620,9931],{"class":125},[112,32622,32623],{"class":163}," Profile",[112,32625,1294],{"class":129},[112,32627,32628,32630,32632],{"class":114,"line":1518},[112,32629,11079],{"class":129},[112,32631,16113],{"class":163},[112,32633,630],{"class":129},[112,32635,32636,32638,32640],{"class":114,"line":1524},[112,32637,30748],{"class":222},[112,32639,2243],{"class":125},[112,32641,16171],{"class":156},[112,32643,32644],{"class":114,"line":1530},[112,32645,147],{"emptyLinePlaceholder":146},[112,32647,32648,32650,32652],{"class":114,"line":1536},[112,32649,11079],{"class":129},[112,32651,16182],{"class":163},[112,32653,630],{"class":129},[112,32655,32656,32658,32660],{"class":114,"line":1542},[112,32657,32285],{"class":222},[112,32659,2243],{"class":125},[112,32661,10006],{"class":156},[112,32663,32664],{"class":114,"line":2402},[112,32665,147],{"emptyLinePlaceholder":146},[112,32667,32668,32670,32672,32674,32676,32679,32682,32684,32686],{"class":114,"line":2407},[112,32669,11079],{"class":129},[112,32671,32539],{"class":163},[112,32673,11758],{"class":129},[112,32675,229],{"class":125},[112,32677,32678],{"class":129}," User, (",[112,32680,32681],{"class":222},"user",[112,32683,226],{"class":129},[112,32685,229],{"class":125},[112,32687,32688],{"class":129}," user.profile)\n",[112,32690,32691,32694,32696],{"class":114,"line":2413},[112,32692,32693],{"class":222},"  user",[112,32695,2243],{"class":125},[112,32697,32698],{"class":163}," User\n",[112,32700,32701],{"class":114,"line":2446},[112,32702,1452],{"class":129},[83,32704,32706],{"id":32705},"one-to-many-many-to-one1対多","One-to-Many \u002F Many-to-One（1対多）",[103,32708,32710],{"className":105,"code":32709,"language":107,"meta":108,"style":108},"\u002F\u002F User Entity（親）\n@Entity()\nexport class User {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column()\n  name: string\n\n  @OneToMany(() => Post, (post) => post.user, {\n    cascade: true, \u002F\u002F Userを保存時にPostも自動保存\n  })\n  posts: Post[]\n}\n\n\u002F\u002F Post Entity（子）\n@Entity()\nexport class Post {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column()\n  title: string\n\n  @ManyToOne(() => User, (user) => user.posts, {\n    onDelete: 'CASCADE', \u002F\u002F Userが削除されたらPostも削除\n  })\n  @JoinColumn({ name: 'user_id' })\n  user: User\n\n  @Column()\n  userId: number \u002F\u002F 外部キーのカラム\n}\n",[90,32711,32712,32717,32725,32735,32743,32751,32755,32763,32771,32775,32798,32809,32813,32825,32829,32833,32838,32846,32856,32864,32872,32876,32884,32892,32896,32918,32931,32935,32949,32957,32961,32969,32980],{"__ignoreMap":108},[112,32713,32714],{"class":114,"line":115},[112,32715,32716],{"class":118},"\u002F\u002F User Entity（親）\n",[112,32718,32719,32721,32723],{"class":114,"line":122},[112,32720,9901],{"class":129},[112,32722,16089],{"class":163},[112,32724,630],{"class":129},[112,32726,32727,32729,32731,32733],{"class":114,"line":143},[112,32728,288],{"class":125},[112,32730,9931],{"class":125},[112,32732,16104],{"class":163},[112,32734,1294],{"class":129},[112,32736,32737,32739,32741],{"class":114,"line":150},[112,32738,11079],{"class":129},[112,32740,16113],{"class":163},[112,32742,630],{"class":129},[112,32744,32745,32747,32749],{"class":114,"line":170},[112,32746,30748],{"class":222},[112,32748,2243],{"class":125},[112,32750,16171],{"class":156},[112,32752,32753],{"class":114,"line":182},[112,32754,147],{"emptyLinePlaceholder":146},[112,32756,32757,32759,32761],{"class":114,"line":193},[112,32758,11079],{"class":129},[112,32760,16182],{"class":163},[112,32762,630],{"class":129},[112,32764,32765,32767,32769],{"class":114,"line":205},[112,32766,10020],{"class":222},[112,32768,2243],{"class":125},[112,32770,10006],{"class":156},[112,32772,32773],{"class":114,"line":241},[112,32774,147],{"emptyLinePlaceholder":146},[112,32776,32777,32779,32782,32784,32786,32789,32791,32793,32795],{"class":114,"line":247},[112,32778,11079],{"class":129},[112,32780,32781],{"class":163},"OneToMany",[112,32783,11758],{"class":129},[112,32785,229],{"class":125},[112,32787,32788],{"class":129}," Post, (",[112,32790,570],{"class":222},[112,32792,226],{"class":129},[112,32794,229],{"class":125},[112,32796,32797],{"class":129}," post.user, {\n",[112,32799,32800,32802,32804,32806],{"class":114,"line":351},[112,32801,32560],{"class":129},[112,32803,4345],{"class":156},[112,32805,447],{"class":129},[112,32807,32808],{"class":118},"\u002F\u002F Userを保存時にPostも自動保存\n",[112,32810,32811],{"class":114,"line":361},[112,32812,8004],{"class":129},[112,32814,32815,32818,32820,32822],{"class":114,"line":367},[112,32816,32817],{"class":222},"  posts",[112,32819,2243],{"class":125},[112,32821,32326],{"class":163},[112,32823,32824],{"class":129},"[]\n",[112,32826,32827],{"class":114,"line":373},[112,32828,1452],{"class":129},[112,32830,32831],{"class":114,"line":379},[112,32832,147],{"emptyLinePlaceholder":146},[112,32834,32835],{"class":114,"line":1302},[112,32836,32837],{"class":118},"\u002F\u002F Post Entity（子）\n",[112,32839,32840,32842,32844],{"class":114,"line":1502},[112,32841,9901],{"class":129},[112,32843,16089],{"class":163},[112,32845,630],{"class":129},[112,32847,32848,32850,32852,32854],{"class":114,"line":1507},[112,32849,288],{"class":125},[112,32851,9931],{"class":125},[112,32853,32326],{"class":163},[112,32855,1294],{"class":129},[112,32857,32858,32860,32862],{"class":114,"line":1512},[112,32859,11079],{"class":129},[112,32861,16113],{"class":163},[112,32863,630],{"class":129},[112,32865,32866,32868,32870],{"class":114,"line":1518},[112,32867,30748],{"class":222},[112,32869,2243],{"class":125},[112,32871,16171],{"class":156},[112,32873,32874],{"class":114,"line":1524},[112,32875,147],{"emptyLinePlaceholder":146},[112,32877,32878,32880,32882],{"class":114,"line":1530},[112,32879,11079],{"class":129},[112,32881,16182],{"class":163},[112,32883,630],{"class":129},[112,32885,32886,32888,32890],{"class":114,"line":1536},[112,32887,32361],{"class":222},[112,32889,2243],{"class":125},[112,32891,10006],{"class":156},[112,32893,32894],{"class":114,"line":1542},[112,32895,147],{"emptyLinePlaceholder":146},[112,32897,32898,32900,32903,32905,32907,32909,32911,32913,32915],{"class":114,"line":2402},[112,32899,11079],{"class":129},[112,32901,32902],{"class":163},"ManyToOne",[112,32904,11758],{"class":129},[112,32906,229],{"class":125},[112,32908,32678],{"class":129},[112,32910,32681],{"class":222},[112,32912,226],{"class":129},[112,32914,229],{"class":125},[112,32916,32917],{"class":129}," user.posts, {\n",[112,32919,32920,32923,32926,32928],{"class":114,"line":2407},[112,32921,32922],{"class":129},"    onDelete: ",[112,32924,32925],{"class":136},"'CASCADE'",[112,32927,447],{"class":129},[112,32929,32930],{"class":118},"\u002F\u002F Userが削除されたらPostも削除\n",[112,32932,32933],{"class":114,"line":2413},[112,32934,8004],{"class":129},[112,32936,32937,32939,32941,32944,32947],{"class":114,"line":2446},[112,32938,11079],{"class":129},[112,32940,32578],{"class":163},[112,32942,32943],{"class":129},"({ name: ",[112,32945,32946],{"class":136},"'user_id'",[112,32948,11585],{"class":129},[112,32950,32951,32953,32955],{"class":114,"line":2451},[112,32952,32693],{"class":222},[112,32954,2243],{"class":125},[112,32956,32698],{"class":163},[112,32958,32959],{"class":114,"line":2464},[112,32960,147],{"emptyLinePlaceholder":146},[112,32962,32963,32965,32967],{"class":114,"line":2481},[112,32964,11079],{"class":129},[112,32966,16182],{"class":163},[112,32968,630],{"class":129},[112,32970,32971,32973,32975,32977],{"class":114,"line":2486},[112,32972,10049],{"class":222},[112,32974,2243],{"class":125},[112,32976,17353],{"class":156},[112,32978,32979],{"class":118}," \u002F\u002F 外部キーのカラム\n",[112,32981,32982],{"class":114,"line":3229},[112,32983,1452],{"class":129},[83,32985,32987],{"id":32986},"many-to-many多対多","Many-to-Many（多対多）",[103,32989,32991],{"className":105,"code":32990,"language":107,"meta":108,"style":108},"\u002F\u002F Student Entity\n@Entity()\nexport class Student {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column()\n  name: string\n\n  @ManyToMany(() => Course, (course) => course.students)\n  @JoinTable({ \u002F\u002F 中間テーブルを作成する側に付ける\n    name: 'student_courses',\n    joinColumn: { name: 'student_id' },\n    inverseJoinColumn: { name: 'course_id' },\n  })\n  courses: Course[]\n}\n\n\u002F\u002F Course Entity\n@Entity()\nexport class Course {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column()\n  title: string\n\n  @ManyToMany(() => Student, (student) => student.courses)\n  students: Student[]\n}\n",[90,32992,32993,32998,33006,33017,33025,33033,33037,33045,33053,33057,33081,33094,33103,33113,33123,33127,33139,33143,33147,33152,33160,33170,33178,33186,33190,33198,33206,33210,33233,33244],{"__ignoreMap":108},[112,32994,32995],{"class":114,"line":115},[112,32996,32997],{"class":118},"\u002F\u002F Student Entity\n",[112,32999,33000,33002,33004],{"class":114,"line":122},[112,33001,9901],{"class":129},[112,33003,16089],{"class":163},[112,33005,630],{"class":129},[112,33007,33008,33010,33012,33015],{"class":114,"line":143},[112,33009,288],{"class":125},[112,33011,9931],{"class":125},[112,33013,33014],{"class":163}," Student",[112,33016,1294],{"class":129},[112,33018,33019,33021,33023],{"class":114,"line":150},[112,33020,11079],{"class":129},[112,33022,16113],{"class":163},[112,33024,630],{"class":129},[112,33026,33027,33029,33031],{"class":114,"line":170},[112,33028,30748],{"class":222},[112,33030,2243],{"class":125},[112,33032,16171],{"class":156},[112,33034,33035],{"class":114,"line":182},[112,33036,147],{"emptyLinePlaceholder":146},[112,33038,33039,33041,33043],{"class":114,"line":193},[112,33040,11079],{"class":129},[112,33042,16182],{"class":163},[112,33044,630],{"class":129},[112,33046,33047,33049,33051],{"class":114,"line":205},[112,33048,10020],{"class":222},[112,33050,2243],{"class":125},[112,33052,10006],{"class":156},[112,33054,33055],{"class":114,"line":241},[112,33056,147],{"emptyLinePlaceholder":146},[112,33058,33059,33061,33064,33066,33068,33071,33074,33076,33078],{"class":114,"line":247},[112,33060,11079],{"class":129},[112,33062,33063],{"class":163},"ManyToMany",[112,33065,11758],{"class":129},[112,33067,229],{"class":125},[112,33069,33070],{"class":129}," Course, (",[112,33072,33073],{"class":222},"course",[112,33075,226],{"class":129},[112,33077,229],{"class":125},[112,33079,33080],{"class":129}," course.students)\n",[112,33082,33083,33085,33088,33091],{"class":114,"line":351},[112,33084,11079],{"class":129},[112,33086,33087],{"class":163},"JoinTable",[112,33089,33090],{"class":129},"({ ",[112,33092,33093],{"class":118},"\u002F\u002F 中間テーブルを作成する側に付ける\n",[112,33095,33096,33098,33101],{"class":114,"line":361},[112,33097,16120],{"class":129},[112,33099,33100],{"class":136},"'student_courses'",[112,33102,179],{"class":129},[112,33104,33105,33108,33111],{"class":114,"line":367},[112,33106,33107],{"class":129},"    joinColumn: { name: ",[112,33109,33110],{"class":136},"'student_id'",[112,33112,2478],{"class":129},[112,33114,33115,33118,33121],{"class":114,"line":373},[112,33116,33117],{"class":129},"    inverseJoinColumn: { name: ",[112,33119,33120],{"class":136},"'course_id'",[112,33122,2478],{"class":129},[112,33124,33125],{"class":114,"line":379},[112,33126,8004],{"class":129},[112,33128,33129,33132,33134,33137],{"class":114,"line":1302},[112,33130,33131],{"class":222},"  courses",[112,33133,2243],{"class":125},[112,33135,33136],{"class":163}," Course",[112,33138,32824],{"class":129},[112,33140,33141],{"class":114,"line":1502},[112,33142,1452],{"class":129},[112,33144,33145],{"class":114,"line":1507},[112,33146,147],{"emptyLinePlaceholder":146},[112,33148,33149],{"class":114,"line":1512},[112,33150,33151],{"class":118},"\u002F\u002F Course Entity\n",[112,33153,33154,33156,33158],{"class":114,"line":1518},[112,33155,9901],{"class":129},[112,33157,16089],{"class":163},[112,33159,630],{"class":129},[112,33161,33162,33164,33166,33168],{"class":114,"line":1524},[112,33163,288],{"class":125},[112,33165,9931],{"class":125},[112,33167,33136],{"class":163},[112,33169,1294],{"class":129},[112,33171,33172,33174,33176],{"class":114,"line":1530},[112,33173,11079],{"class":129},[112,33175,16113],{"class":163},[112,33177,630],{"class":129},[112,33179,33180,33182,33184],{"class":114,"line":1536},[112,33181,30748],{"class":222},[112,33183,2243],{"class":125},[112,33185,16171],{"class":156},[112,33187,33188],{"class":114,"line":1542},[112,33189,147],{"emptyLinePlaceholder":146},[112,33191,33192,33194,33196],{"class":114,"line":2402},[112,33193,11079],{"class":129},[112,33195,16182],{"class":163},[112,33197,630],{"class":129},[112,33199,33200,33202,33204],{"class":114,"line":2407},[112,33201,32361],{"class":222},[112,33203,2243],{"class":125},[112,33205,10006],{"class":156},[112,33207,33208],{"class":114,"line":2413},[112,33209,147],{"emptyLinePlaceholder":146},[112,33211,33212,33214,33216,33218,33220,33223,33226,33228,33230],{"class":114,"line":2446},[112,33213,11079],{"class":129},[112,33215,33063],{"class":163},[112,33217,11758],{"class":129},[112,33219,229],{"class":125},[112,33221,33222],{"class":129}," Student, (",[112,33224,33225],{"class":222},"student",[112,33227,226],{"class":129},[112,33229,229],{"class":125},[112,33231,33232],{"class":129}," student.courses)\n",[112,33234,33235,33238,33240,33242],{"class":114,"line":2451},[112,33236,33237],{"class":222},"  students",[112,33239,2243],{"class":125},[112,33241,33014],{"class":163},[112,33243,32824],{"class":129},[112,33245,33246],{"class":114,"line":2464},[112,33247,1452],{"class":129},[11,33249,33250],{"id":33250},"リレーションオプションの詳細",[83,33252,33254],{"id":33253},"cascadeカスケード","cascade（カスケード）",[15,33256,33257],{},"親エンティティの操作を子エンティティに伝播させます。",[103,33259,33261],{"className":105,"code":33260,"language":107,"meta":108,"style":108},"@Entity()\nexport class User {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @OneToMany(() => Post, (post) => post.user, {\n    cascade: true, \u002F\u002F insert, update, remove すべて\n    \u002F\u002F または個別に指定\n    \u002F\u002F cascade: ['insert', 'update']\n  })\n  posts: Post[]\n}\n\n\u002F\u002F 使用例\nconst user = new User()\nuser.posts = [new Post(), new Post()]\nawait repository.save(user) \u002F\u002F Userと一緒にPostsも保存される\n",[90,33262,33263,33271,33281,33289,33297,33301,33321,33332,33337,33342,33346,33356,33360,33364,33369,33383,33406],{"__ignoreMap":108},[112,33264,33265,33267,33269],{"class":114,"line":115},[112,33266,9901],{"class":129},[112,33268,16089],{"class":163},[112,33270,630],{"class":129},[112,33272,33273,33275,33277,33279],{"class":114,"line":122},[112,33274,288],{"class":125},[112,33276,9931],{"class":125},[112,33278,16104],{"class":163},[112,33280,1294],{"class":129},[112,33282,33283,33285,33287],{"class":114,"line":143},[112,33284,11079],{"class":129},[112,33286,16113],{"class":163},[112,33288,630],{"class":129},[112,33290,33291,33293,33295],{"class":114,"line":150},[112,33292,30748],{"class":222},[112,33294,2243],{"class":125},[112,33296,16171],{"class":156},[112,33298,33299],{"class":114,"line":170},[112,33300,147],{"emptyLinePlaceholder":146},[112,33302,33303,33305,33307,33309,33311,33313,33315,33317,33319],{"class":114,"line":182},[112,33304,11079],{"class":129},[112,33306,32781],{"class":163},[112,33308,11758],{"class":129},[112,33310,229],{"class":125},[112,33312,32788],{"class":129},[112,33314,570],{"class":222},[112,33316,226],{"class":129},[112,33318,229],{"class":125},[112,33320,32797],{"class":129},[112,33322,33323,33325,33327,33329],{"class":114,"line":193},[112,33324,32560],{"class":129},[112,33326,4345],{"class":156},[112,33328,447],{"class":129},[112,33330,33331],{"class":118},"\u002F\u002F insert, update, remove すべて\n",[112,33333,33334],{"class":114,"line":205},[112,33335,33336],{"class":118},"    \u002F\u002F または個別に指定\n",[112,33338,33339],{"class":114,"line":241},[112,33340,33341],{"class":118},"    \u002F\u002F cascade: ['insert', 'update']\n",[112,33343,33344],{"class":114,"line":247},[112,33345,8004],{"class":129},[112,33347,33348,33350,33352,33354],{"class":114,"line":351},[112,33349,32817],{"class":222},[112,33351,2243],{"class":125},[112,33353,32326],{"class":163},[112,33355,32824],{"class":129},[112,33357,33358],{"class":114,"line":361},[112,33359,1452],{"class":129},[112,33361,33362],{"class":114,"line":367},[112,33363,147],{"emptyLinePlaceholder":146},[112,33365,33366],{"class":114,"line":373},[112,33367,33368],{"class":118},"\u002F\u002F 使用例\n",[112,33370,33371,33373,33375,33377,33379,33381],{"class":114,"line":379},[112,33372,153],{"class":125},[112,33374,10311],{"class":156},[112,33376,160],{"class":125},[112,33378,232],{"class":125},[112,33380,16104],{"class":163},[112,33382,630],{"class":129},[112,33384,33385,33388,33390,33392,33394,33396,33399,33401,33403],{"class":114,"line":1302},[112,33386,33387],{"class":129},"user.posts ",[112,33389,576],{"class":125},[112,33391,4323],{"class":129},[112,33393,14155],{"class":125},[112,33395,32326],{"class":163},[112,33397,33398],{"class":129},"(), ",[112,33400,14155],{"class":125},[112,33402,32326],{"class":163},[112,33404,33405],{"class":129},"()]\n",[112,33407,33408,33410,33413,33415,33418],{"class":114,"line":1502},[112,33409,5015],{"class":125},[112,33411,33412],{"class":129}," repository.",[112,33414,17236],{"class":163},[112,33416,33417],{"class":129},"(user) ",[112,33419,33420],{"class":118},"\u002F\u002F Userと一緒にPostsも保存される\n",[83,33422,33424],{"id":33423},"eager-lazy読み込み方式","eager \u002F lazy（読み込み方式）",[103,33426,33428],{"className":105,"code":33427,"language":107,"meta":108,"style":108},"@Entity()\nexport class User {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  \u002F\u002F Eager Loading: Userを取得時に自動でPostsも取得\n  @OneToMany(() => Post, (post) => post.user, {\n    eager: true,\n  })\n  posts: Post[]\n}\n\n\u002F\u002F Lazy Loading\n@Entity()\nexport class User {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  \u002F\u002F Promise型にする\n  @OneToMany(() => Post, (post) => post.user)\n  posts: Promise\u003CPost[]>\n}\n\n\u002F\u002F 使用例\nconst user = await userRepository.findOne({ where: { id: 1 } })\nconst posts = await user.posts \u002F\u002F Promiseをawaitで解決\n",[90,33429,33430,33438,33448,33456,33464,33468,33473,33493,33502,33506,33516,33520,33524,33529,33537,33547,33555,33563,33567,33572,33593,33608,33612,33616,33620,33643],{"__ignoreMap":108},[112,33431,33432,33434,33436],{"class":114,"line":115},[112,33433,9901],{"class":129},[112,33435,16089],{"class":163},[112,33437,630],{"class":129},[112,33439,33440,33442,33444,33446],{"class":114,"line":122},[112,33441,288],{"class":125},[112,33443,9931],{"class":125},[112,33445,16104],{"class":163},[112,33447,1294],{"class":129},[112,33449,33450,33452,33454],{"class":114,"line":143},[112,33451,11079],{"class":129},[112,33453,16113],{"class":163},[112,33455,630],{"class":129},[112,33457,33458,33460,33462],{"class":114,"line":150},[112,33459,30748],{"class":222},[112,33461,2243],{"class":125},[112,33463,16171],{"class":156},[112,33465,33466],{"class":114,"line":170},[112,33467,147],{"emptyLinePlaceholder":146},[112,33469,33470],{"class":114,"line":182},[112,33471,33472],{"class":118},"  \u002F\u002F Eager Loading: Userを取得時に自動でPostsも取得\n",[112,33474,33475,33477,33479,33481,33483,33485,33487,33489,33491],{"class":114,"line":193},[112,33476,11079],{"class":129},[112,33478,32781],{"class":163},[112,33480,11758],{"class":129},[112,33482,229],{"class":125},[112,33484,32788],{"class":129},[112,33486,570],{"class":222},[112,33488,226],{"class":129},[112,33490,229],{"class":125},[112,33492,32797],{"class":129},[112,33494,33495,33498,33500],{"class":114,"line":205},[112,33496,33497],{"class":129},"    eager: ",[112,33499,4345],{"class":156},[112,33501,179],{"class":129},[112,33503,33504],{"class":114,"line":241},[112,33505,8004],{"class":129},[112,33507,33508,33510,33512,33514],{"class":114,"line":247},[112,33509,32817],{"class":222},[112,33511,2243],{"class":125},[112,33513,32326],{"class":163},[112,33515,32824],{"class":129},[112,33517,33518],{"class":114,"line":351},[112,33519,1452],{"class":129},[112,33521,33522],{"class":114,"line":361},[112,33523,147],{"emptyLinePlaceholder":146},[112,33525,33526],{"class":114,"line":367},[112,33527,33528],{"class":118},"\u002F\u002F Lazy Loading\n",[112,33530,33531,33533,33535],{"class":114,"line":373},[112,33532,9901],{"class":129},[112,33534,16089],{"class":163},[112,33536,630],{"class":129},[112,33538,33539,33541,33543,33545],{"class":114,"line":379},[112,33540,288],{"class":125},[112,33542,9931],{"class":125},[112,33544,16104],{"class":163},[112,33546,1294],{"class":129},[112,33548,33549,33551,33553],{"class":114,"line":1302},[112,33550,11079],{"class":129},[112,33552,16113],{"class":163},[112,33554,630],{"class":129},[112,33556,33557,33559,33561],{"class":114,"line":1502},[112,33558,30748],{"class":222},[112,33560,2243],{"class":125},[112,33562,16171],{"class":156},[112,33564,33565],{"class":114,"line":1507},[112,33566,147],{"emptyLinePlaceholder":146},[112,33568,33569],{"class":114,"line":1512},[112,33570,33571],{"class":118},"  \u002F\u002F Promise型にする\n",[112,33573,33574,33576,33578,33580,33582,33584,33586,33588,33590],{"class":114,"line":1518},[112,33575,11079],{"class":129},[112,33577,32781],{"class":163},[112,33579,11758],{"class":129},[112,33581,229],{"class":125},[112,33583,32788],{"class":129},[112,33585,570],{"class":222},[112,33587,226],{"class":129},[112,33589,229],{"class":125},[112,33591,33592],{"class":129}," post.user)\n",[112,33594,33595,33597,33599,33601,33603,33605],{"class":114,"line":1524},[112,33596,32817],{"class":222},[112,33598,2243],{"class":125},[112,33600,10289],{"class":163},[112,33602,6944],{"class":129},[112,33604,11082],{"class":163},[112,33606,33607],{"class":129},"[]>\n",[112,33609,33610],{"class":114,"line":1530},[112,33611,1452],{"class":129},[112,33613,33614],{"class":114,"line":1536},[112,33615,147],{"emptyLinePlaceholder":146},[112,33617,33618],{"class":114,"line":1542},[112,33619,33368],{"class":118},[112,33621,33622,33624,33626,33628,33630,33633,33635,33638,33640],{"class":114,"line":2402},[112,33623,153],{"class":125},[112,33625,10311],{"class":156},[112,33627,160],{"class":125},[112,33629,419],{"class":125},[112,33631,33632],{"class":129}," userRepository.",[112,33634,17179],{"class":163},[112,33636,33637],{"class":129},"({ where: { id: ",[112,33639,8179],{"class":156},[112,33641,33642],{"class":129}," } })\n",[112,33644,33645,33647,33649,33651,33653,33656],{"class":114,"line":2407},[112,33646,153],{"class":125},[112,33648,414],{"class":156},[112,33650,160],{"class":125},[112,33652,419],{"class":125},[112,33654,33655],{"class":129}," user.posts ",[112,33657,33658],{"class":118},"\u002F\u002F Promiseをawaitで解決\n",[83,33660,33662],{"id":33661},"ondelete-onupdate外部キー制約","onDelete \u002F onUpdate（外部キー制約）",[103,33664,33666],{"className":105,"code":33665,"language":107,"meta":108,"style":108},"@Entity()\nexport class Post {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @ManyToOne(() => User, (user) => user.posts, {\n    onDelete: 'CASCADE',    \u002F\u002F Userが削除されたらPostも削除\n    \u002F\u002F onDelete: 'SET NULL', \u002F\u002F Userが削除されたらuser_idをNULLに\n    \u002F\u002F onDelete: 'RESTRICT', \u002F\u002F Postが存在する場合はUserを削除不可\n    onUpdate: 'CASCADE',    \u002F\u002F UserのIDが変更されたらuser_idも更新\n  })\n  @JoinColumn({ name: 'user_id' })\n  user: User\n}\n",[90,33667,33668,33676,33686,33694,33702,33706,33726,33737,33745,33753,33765,33769,33781,33789],{"__ignoreMap":108},[112,33669,33670,33672,33674],{"class":114,"line":115},[112,33671,9901],{"class":129},[112,33673,16089],{"class":163},[112,33675,630],{"class":129},[112,33677,33678,33680,33682,33684],{"class":114,"line":122},[112,33679,288],{"class":125},[112,33681,9931],{"class":125},[112,33683,32326],{"class":163},[112,33685,1294],{"class":129},[112,33687,33688,33690,33692],{"class":114,"line":143},[112,33689,11079],{"class":129},[112,33691,16113],{"class":163},[112,33693,630],{"class":129},[112,33695,33696,33698,33700],{"class":114,"line":150},[112,33697,30748],{"class":222},[112,33699,2243],{"class":125},[112,33701,16171],{"class":156},[112,33703,33704],{"class":114,"line":170},[112,33705,147],{"emptyLinePlaceholder":146},[112,33707,33708,33710,33712,33714,33716,33718,33720,33722,33724],{"class":114,"line":182},[112,33709,11079],{"class":129},[112,33711,32902],{"class":163},[112,33713,11758],{"class":129},[112,33715,229],{"class":125},[112,33717,32678],{"class":129},[112,33719,32681],{"class":222},[112,33721,226],{"class":129},[112,33723,229],{"class":125},[112,33725,32917],{"class":129},[112,33727,33728,33730,33732,33735],{"class":114,"line":193},[112,33729,32922],{"class":129},[112,33731,32925],{"class":136},[112,33733,33734],{"class":129},",    ",[112,33736,32930],{"class":118},[112,33738,33739,33742],{"class":114,"line":205},[112,33740,33741],{"class":118},"    \u002F\u002F onDelete: 'SET NULL',",[112,33743,33744],{"class":118}," \u002F\u002F Userが削除されたらuser_idをNULLに\n",[112,33746,33747,33750],{"class":114,"line":241},[112,33748,33749],{"class":118},"    \u002F\u002F onDelete: 'RESTRICT',",[112,33751,33752],{"class":118}," \u002F\u002F Postが存在する場合はUserを削除不可\n",[112,33754,33755,33758,33760,33762],{"class":114,"line":247},[112,33756,33757],{"class":129},"    onUpdate: ",[112,33759,32925],{"class":136},[112,33761,33734],{"class":129},[112,33763,33764],{"class":118},"\u002F\u002F UserのIDが変更されたらuser_idも更新\n",[112,33766,33767],{"class":114,"line":351},[112,33768,8004],{"class":129},[112,33770,33771,33773,33775,33777,33779],{"class":114,"line":361},[112,33772,11079],{"class":129},[112,33774,32578],{"class":163},[112,33776,32943],{"class":129},[112,33778,32946],{"class":136},[112,33780,11585],{"class":129},[112,33782,33783,33785,33787],{"class":114,"line":367},[112,33784,32693],{"class":222},[112,33786,2243],{"class":125},[112,33788,32698],{"class":163},[112,33790,33791],{"class":114,"line":373},[112,33792,1452],{"class":129},[11,33794,33795],{"id":33795},"パフォーマンス最適化",[83,33797,33799],{"id":33798},"createforeignkeyconstraints","createForeignKeyConstraints",[15,33801,33802],{},"外部キー制約を作成しないことでパフォーマンスを向上させます。",[103,33804,33806],{"className":105,"code":33805,"language":107,"meta":108,"style":108},"@Entity()\nexport class Post {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @ManyToOne(() => User, (user) => user.posts, {\n    createForeignKeyConstraints: false, \u002F\u002F 外部キー制約を作成しない\n  })\n  @JoinColumn({ name: 'user_id' })\n  user: User\n}\n",[90,33807,33808,33816,33826,33834,33842,33846,33866,33878,33882,33894,33902],{"__ignoreMap":108},[112,33809,33810,33812,33814],{"class":114,"line":115},[112,33811,9901],{"class":129},[112,33813,16089],{"class":163},[112,33815,630],{"class":129},[112,33817,33818,33820,33822,33824],{"class":114,"line":122},[112,33819,288],{"class":125},[112,33821,9931],{"class":125},[112,33823,32326],{"class":163},[112,33825,1294],{"class":129},[112,33827,33828,33830,33832],{"class":114,"line":143},[112,33829,11079],{"class":129},[112,33831,16113],{"class":163},[112,33833,630],{"class":129},[112,33835,33836,33838,33840],{"class":114,"line":150},[112,33837,30748],{"class":222},[112,33839,2243],{"class":125},[112,33841,16171],{"class":156},[112,33843,33844],{"class":114,"line":170},[112,33845,147],{"emptyLinePlaceholder":146},[112,33847,33848,33850,33852,33854,33856,33858,33860,33862,33864],{"class":114,"line":182},[112,33849,11079],{"class":129},[112,33851,32902],{"class":163},[112,33853,11758],{"class":129},[112,33855,229],{"class":125},[112,33857,32678],{"class":129},[112,33859,32681],{"class":222},[112,33861,226],{"class":129},[112,33863,229],{"class":125},[112,33865,32917],{"class":129},[112,33867,33868,33871,33873,33875],{"class":114,"line":193},[112,33869,33870],{"class":129},"    createForeignKeyConstraints: ",[112,33872,10359],{"class":156},[112,33874,447],{"class":129},[112,33876,33877],{"class":118},"\u002F\u002F 外部キー制約を作成しない\n",[112,33879,33880],{"class":114,"line":205},[112,33881,8004],{"class":129},[112,33883,33884,33886,33888,33890,33892],{"class":114,"line":241},[112,33885,11079],{"class":129},[112,33887,32578],{"class":163},[112,33889,32943],{"class":129},[112,33891,32946],{"class":136},[112,33893,11585],{"class":129},[112,33895,33896,33898,33900],{"class":114,"line":247},[112,33897,32693],{"class":222},[112,33899,2243],{"class":125},[112,33901,32698],{"class":163},[112,33903,33904],{"class":114,"line":351},[112,33905,1452],{"class":129},[15,33907,33908],{},[27,33909,21357],{},[31,33911,33912,33915],{},[34,33913,33914],{},"データ整合性はアプリケーション側で保証する必要がある",[34,33916,33917],{},"高速な書き込みが必要な場合に有効",[83,33919,33920],{"id":33920},"persistence",[15,33922,33923],{},"保存時の余分なクエリを抑制します。",[103,33925,33927],{"className":105,"code":33926,"language":107,"meta":108,"style":108},"@Entity()\nexport class Post {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @ManyToOne(() => User, (user) => user.posts, {\n    persistence: false, \u002F\u002F 保存時にリレーションを永続化しない\n  })\n  @JoinColumn({ name: 'user_id' })\n  user: User\n\n  @Column()\n  userId: number \u002F\u002F 外部キーは直接操作\n}\n\n\u002F\u002F 使用例\nconst post = new Post()\npost.title = 'Hello'\npost.userId = 1 \u002F\u002F userオブジェクトではなく、直接IDを設定\nawait repository.save(post) \u002F\u002F 余分なクエリが発生しない\n",[90,33928,33929,33937,33947,33955,33963,33967,33987,33999,34003,34015,34023,34027,34035,34046,34050,34054,34058,34073,34083,34095],{"__ignoreMap":108},[112,33930,33931,33933,33935],{"class":114,"line":115},[112,33932,9901],{"class":129},[112,33934,16089],{"class":163},[112,33936,630],{"class":129},[112,33938,33939,33941,33943,33945],{"class":114,"line":122},[112,33940,288],{"class":125},[112,33942,9931],{"class":125},[112,33944,32326],{"class":163},[112,33946,1294],{"class":129},[112,33948,33949,33951,33953],{"class":114,"line":143},[112,33950,11079],{"class":129},[112,33952,16113],{"class":163},[112,33954,630],{"class":129},[112,33956,33957,33959,33961],{"class":114,"line":150},[112,33958,30748],{"class":222},[112,33960,2243],{"class":125},[112,33962,16171],{"class":156},[112,33964,33965],{"class":114,"line":170},[112,33966,147],{"emptyLinePlaceholder":146},[112,33968,33969,33971,33973,33975,33977,33979,33981,33983,33985],{"class":114,"line":182},[112,33970,11079],{"class":129},[112,33972,32902],{"class":163},[112,33974,11758],{"class":129},[112,33976,229],{"class":125},[112,33978,32678],{"class":129},[112,33980,32681],{"class":222},[112,33982,226],{"class":129},[112,33984,229],{"class":125},[112,33986,32917],{"class":129},[112,33988,33989,33992,33994,33996],{"class":114,"line":193},[112,33990,33991],{"class":129},"    persistence: ",[112,33993,10359],{"class":156},[112,33995,447],{"class":129},[112,33997,33998],{"class":118},"\u002F\u002F 保存時にリレーションを永続化しない\n",[112,34000,34001],{"class":114,"line":205},[112,34002,8004],{"class":129},[112,34004,34005,34007,34009,34011,34013],{"class":114,"line":241},[112,34006,11079],{"class":129},[112,34008,32578],{"class":163},[112,34010,32943],{"class":129},[112,34012,32946],{"class":136},[112,34014,11585],{"class":129},[112,34016,34017,34019,34021],{"class":114,"line":247},[112,34018,32693],{"class":222},[112,34020,2243],{"class":125},[112,34022,32698],{"class":163},[112,34024,34025],{"class":114,"line":351},[112,34026,147],{"emptyLinePlaceholder":146},[112,34028,34029,34031,34033],{"class":114,"line":361},[112,34030,11079],{"class":129},[112,34032,16182],{"class":163},[112,34034,630],{"class":129},[112,34036,34037,34039,34041,34043],{"class":114,"line":367},[112,34038,10049],{"class":222},[112,34040,2243],{"class":125},[112,34042,17353],{"class":156},[112,34044,34045],{"class":118}," \u002F\u002F 外部キーは直接操作\n",[112,34047,34048],{"class":114,"line":373},[112,34049,1452],{"class":129},[112,34051,34052],{"class":114,"line":379},[112,34053,147],{"emptyLinePlaceholder":146},[112,34055,34056],{"class":114,"line":1302},[112,34057,33368],{"class":118},[112,34059,34060,34062,34065,34067,34069,34071],{"class":114,"line":1502},[112,34061,153],{"class":125},[112,34063,34064],{"class":156}," post",[112,34066,160],{"class":125},[112,34068,232],{"class":125},[112,34070,32326],{"class":163},[112,34072,630],{"class":129},[112,34074,34075,34078,34080],{"class":114,"line":1507},[112,34076,34077],{"class":129},"post.title ",[112,34079,576],{"class":125},[112,34081,34082],{"class":136}," 'Hello'\n",[112,34084,34085,34088,34090,34092],{"class":114,"line":1512},[112,34086,34087],{"class":129},"post.userId ",[112,34089,576],{"class":125},[112,34091,27898],{"class":156},[112,34093,34094],{"class":118}," \u002F\u002F userオブジェクトではなく、直接IDを設定\n",[112,34096,34097,34099,34101,34103,34106],{"class":114,"line":1518},[112,34098,5015],{"class":125},[112,34100,33412],{"class":129},[112,34102,17236],{"class":163},[112,34104,34105],{"class":129},"(post) ",[112,34107,34108],{"class":118},"\u002F\u002F 余分なクエリが発生しない\n",[11,34110,34112],{"id":34111},"constructorの定義","Constructorの定義",[15,34114,34115],{},"オブジェクトの不変条件を満たすために、必須フィールドを受け取るConstructorを定義します。",[103,34117,34119],{"className":105,"code":34118,"language":107,"meta":108,"style":108},"@Entity()\nexport class User {\n  @PrimaryGeneratedColumn()\n  readonly id: number\n\n  @Column()\n  name: string\n\n  @Column()\n  email: string\n\n  @Column({ default: true })\n  isActive: boolean = true\n\n  @CreateDateColumn()\n  readonly createdAt: Date\n\n  constructor(name: string, email: string) {\n    this.name = name\n    this.email = email\n  }\n}\n\n\u002F\u002F 使用例\nconst user = new User('John Doe', 'john@example.com')\nawait repository.save(user)\n",[90,34120,34121,34129,34139,34147,34157,34161,34169,34177,34181,34189,34197,34201,34213,34225,34229,34237,34247,34251,34273,34283,34293,34297,34301,34305,34309,34333],{"__ignoreMap":108},[112,34122,34123,34125,34127],{"class":114,"line":115},[112,34124,9901],{"class":129},[112,34126,16089],{"class":163},[112,34128,630],{"class":129},[112,34130,34131,34133,34135,34137],{"class":114,"line":122},[112,34132,288],{"class":125},[112,34134,9931],{"class":125},[112,34136,16104],{"class":163},[112,34138,1294],{"class":129},[112,34140,34141,34143,34145],{"class":114,"line":143},[112,34142,11079],{"class":129},[112,34144,16113],{"class":163},[112,34146,630],{"class":129},[112,34148,34149,34151,34153,34155],{"class":114,"line":150},[112,34150,16163],{"class":125},[112,34152,16166],{"class":222},[112,34154,2243],{"class":125},[112,34156,16171],{"class":156},[112,34158,34159],{"class":114,"line":170},[112,34160,147],{"emptyLinePlaceholder":146},[112,34162,34163,34165,34167],{"class":114,"line":182},[112,34164,11079],{"class":129},[112,34166,16182],{"class":163},[112,34168,630],{"class":129},[112,34170,34171,34173,34175],{"class":114,"line":193},[112,34172,10020],{"class":222},[112,34174,2243],{"class":125},[112,34176,10006],{"class":156},[112,34178,34179],{"class":114,"line":205},[112,34180,147],{"emptyLinePlaceholder":146},[112,34182,34183,34185,34187],{"class":114,"line":241},[112,34184,11079],{"class":129},[112,34186,16182],{"class":163},[112,34188,630],{"class":129},[112,34190,34191,34193,34195],{"class":114,"line":247},[112,34192,10001],{"class":222},[112,34194,2243],{"class":125},[112,34196,10006],{"class":156},[112,34198,34199],{"class":114,"line":351},[112,34200,147],{"emptyLinePlaceholder":146},[112,34202,34203,34205,34207,34209,34211],{"class":114,"line":361},[112,34204,11079],{"class":129},[112,34206,16182],{"class":163},[112,34208,32245],{"class":129},[112,34210,4345],{"class":156},[112,34212,11585],{"class":129},[112,34214,34215,34217,34219,34221,34223],{"class":114,"line":367},[112,34216,32020],{"class":222},[112,34218,2243],{"class":125},[112,34220,27866],{"class":156},[112,34222,160],{"class":125},[112,34224,13536],{"class":156},[112,34226,34227],{"class":114,"line":373},[112,34228,147],{"emptyLinePlaceholder":146},[112,34230,34231,34233,34235],{"class":114,"line":379},[112,34232,11079],{"class":129},[112,34234,16292],{"class":163},[112,34236,630],{"class":129},[112,34238,34239,34241,34243,34245],{"class":114,"line":1302},[112,34240,16163],{"class":125},[112,34242,32389],{"class":222},[112,34244,2243],{"class":125},[112,34246,32087],{"class":163},[112,34248,34249],{"class":114,"line":1502},[112,34250,147],{"emptyLinePlaceholder":146},[112,34252,34253,34255,34257,34259,34261,34263,34265,34267,34269,34271],{"class":114,"line":1507},[112,34254,10128],{"class":125},[112,34256,425],{"class":129},[112,34258,16383],{"class":222},[112,34260,2243],{"class":125},[112,34262,10478],{"class":156},[112,34264,447],{"class":129},[112,34266,16392],{"class":222},[112,34268,2243],{"class":125},[112,34270,10478],{"class":156},[112,34272,1969],{"class":129},[112,34274,34275,34277,34279,34281],{"class":114,"line":1512},[112,34276,10148],{"class":156},[112,34278,16413],{"class":129},[112,34280,576],{"class":125},[112,34282,16418],{"class":129},[112,34284,34285,34287,34289,34291],{"class":114,"line":1518},[112,34286,10148],{"class":156},[112,34288,16425],{"class":129},[112,34290,576],{"class":125},[112,34292,16430],{"class":129},[112,34294,34295],{"class":114,"line":1524},[112,34296,3232],{"class":129},[112,34298,34299],{"class":114,"line":1530},[112,34300,1452],{"class":129},[112,34302,34303],{"class":114,"line":1536},[112,34304,147],{"emptyLinePlaceholder":146},[112,34306,34307],{"class":114,"line":1542},[112,34308,33368],{"class":118},[112,34310,34311,34313,34315,34317,34319,34321,34323,34326,34328,34331],{"class":114,"line":2402},[112,34312,153],{"class":125},[112,34314,10311],{"class":156},[112,34316,160],{"class":125},[112,34318,232],{"class":125},[112,34320,16104],{"class":163},[112,34322,425],{"class":129},[112,34324,34325],{"class":136},"'John Doe'",[112,34327,447],{"class":129},[112,34329,34330],{"class":136},"'john@example.com'",[112,34332,431],{"class":129},[112,34334,34335,34337,34339,34341],{"class":114,"line":2407},[112,34336,5015],{"class":125},[112,34338,33412],{"class":129},[112,34340,17236],{"class":163},[112,34342,11898],{"class":129},[11,34344,34346],{"id":34345},"mysqlの型対応表","MySQLの型対応表",[7735,34348,34349,34361],{},[7738,34350,34351],{},[7741,34352,34353,34355,34358],{},[7744,34354,1005],{},[7744,34356,34357],{},"MySQL",[7744,34359,34360],{},"TypeORMの型",[7754,34362,34363,34378,34392,34405,34418,34431,34445,34458,34471,34484,34499,34513],{},[7741,34364,34365,34370,34373],{},[7759,34366,34367],{},[90,34368,34369],{},"number",[7759,34371,34372],{},"TINYINT",[7759,34374,34375],{},[90,34376,34377],{},"'tinyint'",[7741,34379,34380,34384,34387],{},[7759,34381,34382],{},[90,34383,34369],{},[7759,34385,34386],{},"SMALLINT",[7759,34388,34389],{},[90,34390,34391],{},"'smallint'",[7741,34393,34394,34398,34401],{},[7759,34395,34396],{},[90,34397,34369],{},[7759,34399,34400],{},"INT",[7759,34402,34403],{},[90,34404,31931],{},[7741,34406,34407,34411,34414],{},[7759,34408,34409],{},[90,34410,34369],{},[7759,34412,34413],{},"BIGINT",[7759,34415,34416],{},[90,34417,16142],{},[7741,34419,34420,34424,34427],{},[7759,34421,34422],{},[90,34423,34369],{},[7759,34425,34426],{},"DECIMAL",[7759,34428,34429],{},[90,34430,31967],{},[7741,34432,34433,34437,34440],{},[7759,34434,34435],{},[90,34436,34369],{},[7759,34438,34439],{},"FLOAT",[7759,34441,34442],{},[90,34443,34444],{},"'float'",[7741,34446,34447,34451,34454],{},[7759,34448,34449],{},[90,34450,199],{},[7759,34452,34453],{},"VARCHAR",[7759,34455,34456],{},[90,34457,16188],{},[7741,34459,34460,34464,34467],{},[7759,34461,34462],{},[90,34463,199],{},[7759,34465,34466],{},"TEXT",[7759,34468,34469],{},[90,34470,32045],{},[7741,34472,34473,34477,34480],{},[7759,34474,34475],{},[90,34476,13497],{},[7759,34478,34479],{},"TINYINT(1)",[7759,34481,34482],{},[90,34483,32008],{},[7741,34485,34486,34491,34494],{},[7759,34487,34488],{},[90,34489,34490],{},"Date",[7759,34492,34493],{},"DATETIME",[7759,34495,34496],{},[90,34497,34498],{},"'datetime'",[7741,34500,34501,34505,34508],{},[7759,34502,34503],{},[90,34504,34490],{},[7759,34506,34507],{},"TIMESTAMP",[7759,34509,34510],{},[90,34511,34512],{},"'timestamp'",[7741,34514,34515,34519,34521],{},[7759,34516,34517],{},[90,34518,188],{},[7759,34520,5289],{},[7759,34522,34523],{},[90,34524,32107],{},[11,34526,34527],{"id":34527},"ベストプラクティス",[83,34529,34531],{"id":34530},"_1-readonlyの活用","1. readonlyの活用",[15,34533,34534,34535,34538],{},"自動生成されるフィールドや変更されないフィールドには",[90,34536,34537],{},"readonly","を付けます。",[103,34540,34542],{"className":105,"code":34541,"language":107,"meta":108,"style":108},"@Entity()\nexport class User {\n  @PrimaryGeneratedColumn()\n  readonly id: number \u002F\u002F 自動生成されるIDは変更不可\n\n  @CreateDateColumn()\n  readonly createdAt: Date \u002F\u002F 作成日時は変更不可\n}\n",[90,34543,34544,34552,34562,34570,34583,34587,34595,34608],{"__ignoreMap":108},[112,34545,34546,34548,34550],{"class":114,"line":115},[112,34547,9901],{"class":129},[112,34549,16089],{"class":163},[112,34551,630],{"class":129},[112,34553,34554,34556,34558,34560],{"class":114,"line":122},[112,34555,288],{"class":125},[112,34557,9931],{"class":125},[112,34559,16104],{"class":163},[112,34561,1294],{"class":129},[112,34563,34564,34566,34568],{"class":114,"line":143},[112,34565,11079],{"class":129},[112,34567,16113],{"class":163},[112,34569,630],{"class":129},[112,34571,34572,34574,34576,34578,34580],{"class":114,"line":150},[112,34573,16163],{"class":125},[112,34575,16166],{"class":222},[112,34577,2243],{"class":125},[112,34579,17353],{"class":156},[112,34581,34582],{"class":118}," \u002F\u002F 自動生成されるIDは変更不可\n",[112,34584,34585],{"class":114,"line":170},[112,34586,147],{"emptyLinePlaceholder":146},[112,34588,34589,34591,34593],{"class":114,"line":182},[112,34590,11079],{"class":129},[112,34592,16292],{"class":163},[112,34594,630],{"class":129},[112,34596,34597,34599,34601,34603,34605],{"class":114,"line":193},[112,34598,16163],{"class":125},[112,34600,32389],{"class":222},[112,34602,2243],{"class":125},[112,34604,235],{"class":163},[112,34606,34607],{"class":118}," \u002F\u002F 作成日時は変更不可\n",[112,34609,34610],{"class":114,"line":205},[112,34611,1452],{"class":129},[83,34613,34615],{"id":34614},"_2-nullの明示的な初期化","2. NULLの明示的な初期化",[15,34617,34618],{},"TypeScriptの型安全性のため、NULL許容カラムには初期値を設定します。",[103,34620,34622],{"className":105,"code":34621,"language":107,"meta":108,"style":108},"@Column({ nullable: true })\nphoneNumber: string | null = null \u002F\u002F 明示的にnullで初期化\n",[90,34623,34624,34636],{"__ignoreMap":108},[112,34625,34626,34628,34630,34632,34634],{"class":114,"line":115},[112,34627,9901],{"class":129},[112,34629,16182],{"class":163},[112,34631,11580],{"class":129},[112,34633,4345],{"class":156},[112,34635,11585],{"class":129},[112,34637,34638,34641,34644,34647,34649,34651,34653],{"class":114,"line":122},[112,34639,34640],{"class":163},"phoneNumber",[112,34642,34643],{"class":129},": string ",[112,34645,34646],{"class":125},"|",[112,34648,3425],{"class":156},[112,34650,160],{"class":125},[112,34652,3425],{"class":156},[112,34654,34655],{"class":118}," \u002F\u002F 明示的にnullで初期化\n",[83,34657,34659],{"id":34658},"_3-commentの活用","3. Commentの活用",[15,34661,34662],{},"データベースのカラムにコメントを付けることでドキュメント化できます。",[103,34664,34666],{"className":105,"code":34665,"language":107,"meta":108,"style":108},"@Column('varchar', { comment: 'ユーザーのメールアドレス' })\nemail: string\n",[90,34667,34668,34685],{"__ignoreMap":108},[112,34669,34670,34672,34674,34676,34678,34680,34683],{"class":114,"line":115},[112,34671,9901],{"class":129},[112,34673,16182],{"class":163},[112,34675,425],{"class":129},[112,34677,16188],{"class":136},[112,34679,24635],{"class":129},[112,34681,34682],{"class":136},"'ユーザーのメールアドレス'",[112,34684,11585],{"class":129},[112,34686,34687,34689],{"class":114,"line":122},[112,34688,16392],{"class":163},[112,34690,34691],{"class":129},": string\n",[83,34693,34695],{"id":34694},"_4-unsigned属性mysql","4. unsigned属性（MySQL）",[15,34697,34698,34699,34702],{},"負の値を扱わない場合は",[90,34700,34701],{},"unsigned","を指定して範囲を拡大します。",[103,34704,34706],{"className":105,"code":34705,"language":107,"meta":108,"style":108},"@Column('int', { unsigned: true }) \u002F\u002F 0 〜 4,294,967,295\nprice: number\n",[90,34707,34708,34727],{"__ignoreMap":108},[112,34709,34710,34712,34714,34716,34718,34720,34722,34724],{"class":114,"line":115},[112,34711,9901],{"class":129},[112,34713,16182],{"class":163},[112,34715,425],{"class":129},[112,34717,31931],{"class":136},[112,34719,31934],{"class":129},[112,34721,4345],{"class":156},[112,34723,2259],{"class":129},[112,34725,34726],{"class":118},"\u002F\u002F 0 〜 4,294,967,295\n",[112,34728,34729,34732],{"class":114,"line":122},[112,34730,34731],{"class":163},"price",[112,34733,34734],{"class":129},": number\n",[11,34736,919],{"id":919},[15,34738,34739],{},"TypeORMのEntityを定義する際のポイントは次の通りです。",[31,34741,34742,34754,34760,34766,34771],{},[34,34743,34744,9512,34746,34749,34750,34753],{},[27,34745,31757],{},[90,34747,34748],{},"@Entity","、",[90,34751,34752],{},"@Column","などでカラムとリレーションを定義",[34,34755,34756,34759],{},[27,34757,34758],{},"型指定"," TypeScriptとデータベースの型を正しくマッピング",[34,34761,34762,34765],{},[27,34763,34764],{},"リレーション"," cascade、eager、onDeleteなどのオプションを理解",[34,34767,34768,34770],{},[27,34769,2724],{}," 外部キー制約や永続化を制御",[34,34772,34773,34775],{},[27,34774,28584],{}," readonly、NULL初期化、Constructorで型安全なコードを実現",[15,34777,34778],{},"Entity定義により、型安全で保守性の高いデータベース操作が可能になります。",[83,34780,2930],{"id":2930},[31,34782,34783,34790,34797],{},[34,34784,34785],{},[759,34786,34789],{"href":34787,"rel":34788},"https:\u002F\u002Ftypeorm.io\u002F",[763],"TypeORM 公式ドキュメント",[34,34791,34792],{},[759,34793,34796],{"href":34794,"rel":34795},"https:\u002F\u002Fgithub.com\u002Ftypeorm\u002Ftypeorm\u002Fblob\u002Fmaster\u002Fdocs\u002Fdecorator-reference.md",[763],"Decorator Reference",[34,34798,34799],{},[759,34800,34803],{"href":34801,"rel":34802},"https:\u002F\u002Ftypeorm.io\u002Frelations",[763],"Relations",[944,34805,34806],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":108,"searchDepth":122,"depth":122,"links":34808},[34809,34810,34814,34819,34824,34829,34833,34834,34835,34841],{"id":31612,"depth":122,"text":31613},{"id":31638,"depth":122,"text":31639,"children":34811},[34812,34813],{"id":31642,"depth":143,"text":31643},{"id":31748,"depth":143,"text":31748},{"id":31834,"depth":122,"text":31834,"children":34815},[34816,34817,34818],{"id":31837,"depth":143,"text":31837},{"id":32131,"depth":143,"text":32132},{"id":32304,"depth":143,"text":32304},{"id":32460,"depth":122,"text":32460,"children":34820},[34821,34822,34823],{"id":32463,"depth":143,"text":32464},{"id":32705,"depth":143,"text":32706},{"id":32986,"depth":143,"text":32987},{"id":33250,"depth":122,"text":33250,"children":34825},[34826,34827,34828],{"id":33253,"depth":143,"text":33254},{"id":33423,"depth":143,"text":33424},{"id":33661,"depth":143,"text":33662},{"id":33795,"depth":122,"text":33795,"children":34830},[34831,34832],{"id":33798,"depth":143,"text":33799},{"id":33920,"depth":143,"text":33920},{"id":34111,"depth":122,"text":34112},{"id":34345,"depth":122,"text":34346},{"id":34527,"depth":122,"text":34527,"children":34836},[34837,34838,34839,34840],{"id":34530,"depth":143,"text":34531},{"id":34614,"depth":143,"text":34615},{"id":34658,"depth":143,"text":34659},{"id":34694,"depth":143,"text":34695},{"id":919,"depth":122,"text":919,"children":34842},[34843],{"id":2930,"depth":143,"text":2930},"TypeORMでEntityを定義する方法を、基本的なデコレータからリレーション、パフォーマンス最適化まで実践的に解説します。",{"tags":34846},[26492,107,34847],"database","\u002Fblog\u002Ftypeorm-entity-basics",{"title":31607,"description":34844},"blog\u002Ftypeorm-entity-basics","sC0JQtf_hF2YHPrixin0P2KkfY3zUjztz25Ya0YPL8k",{"id":34853,"title":34854,"body":34855,"date":29613,"description":36051,"extension":962,"meta":36052,"navigation":146,"path":36054,"seo":36055,"stem":36056,"__hash__":36057},"blog\u002Fblog\u002Ftypeorm-transformer.md","TypeORMのTransformer機能でマスターデータを扱う",{"type":8,"value":34856,"toc":36029},[34857,34860,34863,34869,34873,34877,34880,35109,35113,35281,35285,35291,35536,35539,35542,35585,35588,35657,35661,35665,35668,35715,35719,35722,35768,35772,35775,35777,35781,35784,35937,35940,35943,36007,36009,36012,36023,36026],[11,34858,34859],{"id":34859},"課題",[15,34861,34862],{},"データベースには数値IDで保存されているマスターデータ（都道府県、ステータスなど）を、アプリケーション層ではIDと名前を持つオブジェクトとして扱いたい場合があります。",[15,34864,18799,34865,34868],{},[27,34866,34867],{},"Transformer機能","を使うことで、この変換を自動化できます。",[11,34870,34872],{"id":34871},"実装例都道府県マスター","実装例：都道府県マスター",[83,34874,34876],{"id":34875},"_1-汎用的なmasterクラスを定義","1. 汎用的なMasterクラスを定義",[15,34878,34879],{},"IDと名前を持つマスターデータを管理する基底クラスを作成します。",[103,34881,34883],{"className":26556,"code":34882,"language":26558,"meta":108,"style":108},"export interface IMaster {\n  id: number\n  name: string\n}\n\nexport class Master {\n  private readonly masters: IMaster[]\n\n  constructor(masters: IMaster[]) {\n    this.masters = masters\n  }\n\n  findById(id: number): IMaster | undefined {\n    return this.masters.find((v) => v.id === Number(id))\n  }\n\n  findByName(name: string): IMaster | undefined {\n    return this.masters.find((v) => v.name === name)\n  }\n}\n",[90,34884,34885,34896,34904,34912,34916,34920,34931,34946,34950,34966,34978,34982,34986,35012,35042,35046,35050,35075,35101,35105],{"__ignoreMap":108},[112,34886,34887,34889,34891,34894],{"class":114,"line":115},[112,34888,288],{"class":125},[112,34890,9991],{"class":125},[112,34892,34893],{"class":163}," IMaster",[112,34895,1294],{"class":129},[112,34897,34898,34900,34902],{"class":114,"line":122},[112,34899,30748],{"class":222},[112,34901,2243],{"class":125},[112,34903,16171],{"class":156},[112,34905,34906,34908,34910],{"class":114,"line":143},[112,34907,10020],{"class":222},[112,34909,2243],{"class":125},[112,34911,10006],{"class":156},[112,34913,34914],{"class":114,"line":150},[112,34915,1452],{"class":129},[112,34917,34918],{"class":114,"line":170},[112,34919,147],{"emptyLinePlaceholder":146},[112,34921,34922,34924,34926,34929],{"class":114,"line":182},[112,34923,288],{"class":125},[112,34925,9931],{"class":125},[112,34927,34928],{"class":163}," Master",[112,34930,1294],{"class":129},[112,34932,34933,34935,34937,34940,34942,34944],{"class":114,"line":193},[112,34934,10094],{"class":125},[112,34936,10097],{"class":125},[112,34938,34939],{"class":222}," masters",[112,34941,2243],{"class":125},[112,34943,34893],{"class":163},[112,34945,32824],{"class":129},[112,34947,34948],{"class":114,"line":205},[112,34949,147],{"emptyLinePlaceholder":146},[112,34951,34952,34954,34956,34959,34961,34963],{"class":114,"line":241},[112,34953,10128],{"class":125},[112,34955,425],{"class":129},[112,34957,34958],{"class":222},"masters",[112,34960,2243],{"class":125},[112,34962,34893],{"class":163},[112,34964,34965],{"class":129},"[]) {\n",[112,34967,34968,34970,34973,34975],{"class":114,"line":247},[112,34969,10148],{"class":156},[112,34971,34972],{"class":129},".masters ",[112,34974,576],{"class":125},[112,34976,34977],{"class":129}," masters\n",[112,34979,34980],{"class":114,"line":351},[112,34981,3232],{"class":129},[112,34983,34984],{"class":114,"line":361},[112,34985,147],{"emptyLinePlaceholder":146},[112,34987,34988,34991,34993,34995,34997,34999,35001,35003,35005,35007,35010],{"class":114,"line":367},[112,34989,34990],{"class":163},"  findById",[112,34992,425],{"class":129},[112,34994,17348],{"class":222},[112,34996,2243],{"class":125},[112,34998,17353],{"class":156},[112,35000,10186],{"class":129},[112,35002,2243],{"class":125},[112,35004,34893],{"class":163},[112,35006,32224],{"class":125},[112,35008,35009],{"class":156}," undefined",[112,35011,1294],{"class":129},[112,35013,35014,35016,35018,35021,35023,35025,35027,35029,35031,35034,35036,35039],{"class":114,"line":373},[112,35015,3973],{"class":125},[112,35017,10318],{"class":156},[112,35019,35020],{"class":129},".masters.",[112,35022,17326],{"class":163},[112,35024,219],{"class":129},[112,35026,4181],{"class":222},[112,35028,226],{"class":129},[112,35030,229],{"class":125},[112,35032,35033],{"class":129}," v.id ",[112,35035,3624],{"class":125},[112,35037,35038],{"class":163}," Number",[112,35040,35041],{"class":129},"(id))\n",[112,35043,35044],{"class":114,"line":379},[112,35045,3232],{"class":129},[112,35047,35048],{"class":114,"line":1302},[112,35049,147],{"emptyLinePlaceholder":146},[112,35051,35052,35055,35057,35059,35061,35063,35065,35067,35069,35071,35073],{"class":114,"line":1502},[112,35053,35054],{"class":163},"  findByName",[112,35056,425],{"class":129},[112,35058,16383],{"class":222},[112,35060,2243],{"class":125},[112,35062,10478],{"class":156},[112,35064,10186],{"class":129},[112,35066,2243],{"class":125},[112,35068,34893],{"class":163},[112,35070,32224],{"class":125},[112,35072,35009],{"class":156},[112,35074,1294],{"class":129},[112,35076,35077,35079,35081,35083,35085,35087,35089,35091,35093,35096,35098],{"class":114,"line":1507},[112,35078,3973],{"class":125},[112,35080,10318],{"class":156},[112,35082,35020],{"class":129},[112,35084,17326],{"class":163},[112,35086,219],{"class":129},[112,35088,4181],{"class":222},[112,35090,226],{"class":129},[112,35092,229],{"class":125},[112,35094,35095],{"class":129}," v.name ",[112,35097,3624],{"class":125},[112,35099,35100],{"class":129}," name)\n",[112,35102,35103],{"class":114,"line":1512},[112,35104,3232],{"class":129},[112,35106,35107],{"class":114,"line":1518},[112,35108,1452],{"class":129},[83,35110,35112],{"id":35111},"_2-都道府県マスターを実装","2. 都道府県マスターを実装",[103,35114,35116],{"className":26556,"code":35115,"language":26558,"meta":108,"style":108},"import { IMaster, Master } from '.\u002Fmaster'\n\nconst PREFECTURES: IMaster[] = [\n  { id: 1, name: '北海道' },\n  { id: 2, name: '青森県' },\n  { id: 3, name: '岩手県' },\n  \u002F\u002F ...\n  { id: 47, name: '沖縄県' },\n]\n\nclass PrefectureMaster extends Master {\n  constructor() {\n    super(PREFECTURES)\n  }\n}\n\nexport const Prefecture = new PrefectureMaster()\n",[90,35117,35118,35130,35134,35152,35167,35180,35193,35197,35211,35215,35219,35234,35240,35252,35256,35260,35264],{"__ignoreMap":108},[112,35119,35120,35122,35125,35127],{"class":114,"line":115},[112,35121,126],{"class":125},[112,35123,35124],{"class":129}," { IMaster, Master } ",[112,35126,133],{"class":125},[112,35128,35129],{"class":136}," '.\u002Fmaster'\n",[112,35131,35132],{"class":114,"line":122},[112,35133,147],{"emptyLinePlaceholder":146},[112,35135,35136,35138,35141,35143,35145,35148,35150],{"class":114,"line":143},[112,35137,153],{"class":125},[112,35139,35140],{"class":156}," PREFECTURES",[112,35142,2243],{"class":125},[112,35144,34893],{"class":163},[112,35146,35147],{"class":129},"[] ",[112,35149,576],{"class":125},[112,35151,26827],{"class":129},[112,35153,35154,35157,35159,35162,35165],{"class":114,"line":150},[112,35155,35156],{"class":129},"  { id: ",[112,35158,8179],{"class":156},[112,35160,35161],{"class":129},", name: ",[112,35163,35164],{"class":136},"'北海道'",[112,35166,2478],{"class":129},[112,35168,35169,35171,35173,35175,35178],{"class":114,"line":170},[112,35170,35156],{"class":129},[112,35172,19956],{"class":156},[112,35174,35161],{"class":129},[112,35176,35177],{"class":136},"'青森県'",[112,35179,2478],{"class":129},[112,35181,35182,35184,35186,35188,35191],{"class":114,"line":182},[112,35183,35156],{"class":129},[112,35185,2975],{"class":156},[112,35187,35161],{"class":129},[112,35189,35190],{"class":136},"'岩手県'",[112,35192,2478],{"class":129},[112,35194,35195],{"class":114,"line":193},[112,35196,15965],{"class":118},[112,35198,35199,35201,35204,35206,35209],{"class":114,"line":205},[112,35200,35156],{"class":129},[112,35202,35203],{"class":156},"47",[112,35205,35161],{"class":129},[112,35207,35208],{"class":136},"'沖縄県'",[112,35210,2478],{"class":129},[112,35212,35213],{"class":114,"line":241},[112,35214,19410],{"class":129},[112,35216,35217],{"class":114,"line":247},[112,35218,147],{"emptyLinePlaceholder":146},[112,35220,35221,35224,35227,35230,35232],{"class":114,"line":351},[112,35222,35223],{"class":125},"class",[112,35225,35226],{"class":163}," PrefectureMaster",[112,35228,35229],{"class":125}," extends",[112,35231,34928],{"class":163},[112,35233,1294],{"class":129},[112,35235,35236,35238],{"class":114,"line":361},[112,35237,10128],{"class":125},[112,35239,3313],{"class":129},[112,35241,35242,35245,35247,35250],{"class":114,"line":367},[112,35243,35244],{"class":156},"    super",[112,35246,425],{"class":129},[112,35248,35249],{"class":156},"PREFECTURES",[112,35251,431],{"class":129},[112,35253,35254],{"class":114,"line":373},[112,35255,3232],{"class":129},[112,35257,35258],{"class":114,"line":379},[112,35259,1452],{"class":129},[112,35261,35262],{"class":114,"line":1302},[112,35263,147],{"emptyLinePlaceholder":146},[112,35265,35266,35268,35270,35273,35275,35277,35279],{"class":114,"line":1502},[112,35267,288],{"class":125},[112,35269,1286],{"class":125},[112,35271,35272],{"class":156}," Prefecture",[112,35274,160],{"class":125},[112,35276,232],{"class":125},[112,35278,35226],{"class":163},[112,35280,630],{"class":129},[83,35282,35284],{"id":35283},"_3-entityでtransformerを使用","3. EntityでTransformerを使用",[15,35286,35287,35290],{},[90,35288,35289],{},"transformer","オプションを指定することで、データベースとアプリケーション間の変換を自動化できます。",[103,35292,35294],{"className":26556,"code":35293,"language":26558,"meta":108,"style":108},"import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'\nimport { Prefecture } from '.\u002Fprefecture.master'\nimport { IMaster } from '.\u002Fmaster'\n\n@Entity()\nexport class User {\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column('varchar')\n  name: string\n\n  @Column('tinyint', {\n    unsigned: true,\n    comment: '都道府県ID',\n    transformer: {\n      \u002F\u002F DB → アプリケーション: IDからIMasterオブジェクトへ\n      from(id: number): IMaster | undefined {\n        return Prefecture.findById(id)\n      },\n      \u002F\u002F アプリケーション → DB: IMasterオブジェクトからIDへ\n      to(value: IMaster): number {\n        return value.id\n      },\n    },\n  })\n  prefecture: IMaster\n}\n",[90,35295,35296,35307,35319,35330,35334,35342,35352,35360,35368,35372,35384,35392,35396,35408,35416,35425,35430,35435,35460,35472,35476,35481,35503,35510,35514,35518,35522,35532],{"__ignoreMap":108},[112,35297,35298,35300,35303,35305],{"class":114,"line":115},[112,35299,126],{"class":125},[112,35301,35302],{"class":129}," { Entity, Column, PrimaryGeneratedColumn } ",[112,35304,133],{"class":125},[112,35306,16078],{"class":136},[112,35308,35309,35311,35314,35316],{"class":114,"line":122},[112,35310,126],{"class":125},[112,35312,35313],{"class":129}," { Prefecture } ",[112,35315,133],{"class":125},[112,35317,35318],{"class":136}," '.\u002Fprefecture.master'\n",[112,35320,35321,35323,35326,35328],{"class":114,"line":143},[112,35322,126],{"class":125},[112,35324,35325],{"class":129}," { IMaster } ",[112,35327,133],{"class":125},[112,35329,35129],{"class":136},[112,35331,35332],{"class":114,"line":150},[112,35333,147],{"emptyLinePlaceholder":146},[112,35335,35336,35338,35340],{"class":114,"line":170},[112,35337,9901],{"class":129},[112,35339,16089],{"class":163},[112,35341,630],{"class":129},[112,35343,35344,35346,35348,35350],{"class":114,"line":182},[112,35345,288],{"class":125},[112,35347,9931],{"class":125},[112,35349,16104],{"class":163},[112,35351,1294],{"class":129},[112,35353,35354,35356,35358],{"class":114,"line":193},[112,35355,11079],{"class":129},[112,35357,16113],{"class":163},[112,35359,630],{"class":129},[112,35361,35362,35364,35366],{"class":114,"line":205},[112,35363,30748],{"class":222},[112,35365,2243],{"class":125},[112,35367,16171],{"class":156},[112,35369,35370],{"class":114,"line":241},[112,35371,147],{"emptyLinePlaceholder":146},[112,35373,35374,35376,35378,35380,35382],{"class":114,"line":247},[112,35375,11079],{"class":129},[112,35377,16182],{"class":163},[112,35379,425],{"class":129},[112,35381,16188],{"class":136},[112,35383,431],{"class":129},[112,35385,35386,35388,35390],{"class":114,"line":351},[112,35387,10020],{"class":222},[112,35389,2243],{"class":125},[112,35391,10006],{"class":156},[112,35393,35394],{"class":114,"line":361},[112,35395,147],{"emptyLinePlaceholder":146},[112,35397,35398,35400,35402,35404,35406],{"class":114,"line":367},[112,35399,11079],{"class":129},[112,35401,16182],{"class":163},[112,35403,425],{"class":129},[112,35405,34377],{"class":136},[112,35407,5239],{"class":129},[112,35409,35410,35412,35414],{"class":114,"line":373},[112,35411,16130],{"class":129},[112,35413,4345],{"class":156},[112,35415,179],{"class":129},[112,35417,35418,35420,35423],{"class":114,"line":379},[112,35419,16149],{"class":129},[112,35421,35422],{"class":136},"'都道府県ID'",[112,35424,179],{"class":129},[112,35426,35427],{"class":114,"line":1302},[112,35428,35429],{"class":129},"    transformer: {\n",[112,35431,35432],{"class":114,"line":1502},[112,35433,35434],{"class":118},"      \u002F\u002F DB → アプリケーション: IDからIMasterオブジェクトへ\n",[112,35436,35437,35440,35442,35444,35446,35448,35450,35452,35454,35456,35458],{"class":114,"line":1507},[112,35438,35439],{"class":163},"      from",[112,35441,425],{"class":129},[112,35443,17348],{"class":222},[112,35445,2243],{"class":125},[112,35447,17353],{"class":156},[112,35449,10186],{"class":129},[112,35451,2243],{"class":125},[112,35453,34893],{"class":163},[112,35455,32224],{"class":125},[112,35457,35009],{"class":156},[112,35459,1294],{"class":129},[112,35461,35462,35464,35467,35470],{"class":114,"line":1512},[112,35463,21139],{"class":125},[112,35465,35466],{"class":129}," Prefecture.",[112,35468,35469],{"class":163},"findById",[112,35471,17379],{"class":129},[112,35473,35474],{"class":114,"line":1518},[112,35475,2373],{"class":129},[112,35477,35478],{"class":114,"line":1524},[112,35479,35480],{"class":118},"      \u002F\u002F アプリケーション → DB: IMasterオブジェクトからIDへ\n",[112,35482,35483,35486,35488,35491,35493,35495,35497,35499,35501],{"class":114,"line":1530},[112,35484,35485],{"class":163},"      to",[112,35487,425],{"class":129},[112,35489,35490],{"class":222},"value",[112,35492,2243],{"class":125},[112,35494,34893],{"class":163},[112,35496,10186],{"class":129},[112,35498,2243],{"class":125},[112,35500,17353],{"class":156},[112,35502,1294],{"class":129},[112,35504,35505,35507],{"class":114,"line":1536},[112,35506,21139],{"class":125},[112,35508,35509],{"class":129}," value.id\n",[112,35511,35512],{"class":114,"line":1542},[112,35513,2373],{"class":129},[112,35515,35516],{"class":114,"line":2402},[112,35517,2378],{"class":129},[112,35519,35520],{"class":114,"line":2407},[112,35521,8004],{"class":129},[112,35523,35524,35527,35529],{"class":114,"line":2413},[112,35525,35526],{"class":222},"  prefecture",[112,35528,2243],{"class":125},[112,35530,35531],{"class":163}," IMaster\n",[112,35533,35534],{"class":114,"line":2446},[112,35535,1452],{"class":129},[11,35537,35538],{"id":35538},"使用例",[83,35540,35541],{"id":35541},"データの取得",[103,35543,35545],{"className":26556,"code":35544,"language":26558,"meta":108,"style":108},"const user = await userRepository.findOne({ where: { id: 1 } })\n\nconsole.log(user.prefecture)\n\u002F\u002F { id: 13, name: '東京都' }\n",[90,35546,35547,35567,35571,35580],{"__ignoreMap":108},[112,35548,35549,35551,35553,35555,35557,35559,35561,35563,35565],{"class":114,"line":115},[112,35550,153],{"class":125},[112,35552,10311],{"class":156},[112,35554,160],{"class":125},[112,35556,419],{"class":125},[112,35558,33632],{"class":129},[112,35560,17179],{"class":163},[112,35562,33637],{"class":129},[112,35564,8179],{"class":156},[112,35566,33642],{"class":129},[112,35568,35569],{"class":114,"line":122},[112,35570,147],{"emptyLinePlaceholder":146},[112,35572,35573,35575,35577],{"class":114,"line":143},[112,35574,26612],{"class":129},[112,35576,26615],{"class":163},[112,35578,35579],{"class":129},"(user.prefecture)\n",[112,35581,35582],{"class":114,"line":150},[112,35583,35584],{"class":118},"\u002F\u002F { id: 13, name: '東京都' }\n",[83,35586,35587],{"id":35587},"データの保存",[103,35589,35591],{"className":26556,"code":35590,"language":26558,"meta":108,"style":108},"const newUser = userRepository.create({\n  name: '山田太郎',\n  prefecture: { id: 13, name: '東京都' },\n})\n\nawait userRepository.save(newUser)\n\u002F\u002F DBには prefecture = 13 として保存される\n",[90,35592,35593,35608,35618,35633,35637,35641,35652],{"__ignoreMap":108},[112,35594,35595,35597,35600,35602,35604,35606],{"class":114,"line":115},[112,35596,153],{"class":125},[112,35598,35599],{"class":156}," newUser",[112,35601,160],{"class":125},[112,35603,33632],{"class":129},[112,35605,14070],{"class":163},[112,35607,167],{"class":129},[112,35609,35610,35613,35616],{"class":114,"line":122},[112,35611,35612],{"class":129},"  name: ",[112,35614,35615],{"class":136},"'山田太郎'",[112,35617,179],{"class":129},[112,35619,35620,35623,35626,35628,35631],{"class":114,"line":143},[112,35621,35622],{"class":129},"  prefecture: { id: ",[112,35624,35625],{"class":156},"13",[112,35627,35161],{"class":129},[112,35629,35630],{"class":136},"'東京都'",[112,35632,2478],{"class":129},[112,35634,35635],{"class":114,"line":150},[112,35636,8436],{"class":129},[112,35638,35639],{"class":114,"line":170},[112,35640,147],{"emptyLinePlaceholder":146},[112,35642,35643,35645,35647,35649],{"class":114,"line":182},[112,35644,5015],{"class":125},[112,35646,33632],{"class":129},[112,35648,17236],{"class":163},[112,35650,35651],{"class":129},"(newUser)\n",[112,35653,35654],{"class":114,"line":193},[112,35655,35656],{"class":118},"\u002F\u002F DBには prefecture = 13 として保存される\n",[11,35658,35660],{"id":35659},"transformerのメリット","Transformerのメリット",[83,35662,35664],{"id":35663},"_1-型安全性","1. 型安全性",[15,35666,35667],{},"IDを直接扱うのではなく、オブジェクトとして扱うことで型安全性が向上します。",[103,35669,35671],{"className":26556,"code":35670,"language":26558,"meta":108,"style":108},"\u002F\u002F ❌ IDだけでは何のIDか不明\nuser.prefecture_id = 13\n\n\u002F\u002F ✅ オブジェクトなので明確\nuser.prefecture = { id: 13, name: '東京都' }\n",[90,35672,35673,35678,35688,35692,35697],{"__ignoreMap":108},[112,35674,35675],{"class":114,"line":115},[112,35676,35677],{"class":118},"\u002F\u002F ❌ IDだけでは何のIDか不明\n",[112,35679,35680,35683,35685],{"class":114,"line":122},[112,35681,35682],{"class":129},"user.prefecture_id ",[112,35684,576],{"class":125},[112,35686,35687],{"class":156}," 13\n",[112,35689,35690],{"class":114,"line":143},[112,35691,147],{"emptyLinePlaceholder":146},[112,35693,35694],{"class":114,"line":150},[112,35695,35696],{"class":118},"\u002F\u002F ✅ オブジェクトなので明確\n",[112,35698,35699,35702,35704,35707,35709,35711,35713],{"class":114,"line":170},[112,35700,35701],{"class":129},"user.prefecture ",[112,35703,576],{"class":125},[112,35705,35706],{"class":129}," { id: ",[112,35708,35625],{"class":156},[112,35710,35161],{"class":129},[112,35712,35630],{"class":136},[112,35714,2395],{"class":129},[83,35716,35718],{"id":35717},"_2-コードの可読性","2. コードの可読性",[15,35720,35721],{},"マスターデータの名前に直接アクセスできます。",[103,35723,35725],{"className":26556,"code":35724,"language":26558,"meta":108,"style":108},"\u002F\u002F ❌ 別途マスターを参照する必要がある\nconst prefectureName = Prefecture.findById(user.prefecture_id)?.name\n\n\u002F\u002F ✅ 直接アクセス可能\nconst prefectureName = user.prefecture.name\n",[90,35726,35727,35732,35748,35752,35757],{"__ignoreMap":108},[112,35728,35729],{"class":114,"line":115},[112,35730,35731],{"class":118},"\u002F\u002F ❌ 別途マスターを参照する必要がある\n",[112,35733,35734,35736,35739,35741,35743,35745],{"class":114,"line":122},[112,35735,153],{"class":125},[112,35737,35738],{"class":156}," prefectureName",[112,35740,160],{"class":125},[112,35742,35466],{"class":129},[112,35744,35469],{"class":163},[112,35746,35747],{"class":129},"(user.prefecture_id)?.name\n",[112,35749,35750],{"class":114,"line":143},[112,35751,147],{"emptyLinePlaceholder":146},[112,35753,35754],{"class":114,"line":150},[112,35755,35756],{"class":118},"\u002F\u002F ✅ 直接アクセス可能\n",[112,35758,35759,35761,35763,35765],{"class":114,"line":170},[112,35760,153],{"class":125},[112,35762,35738],{"class":156},[112,35764,160],{"class":125},[112,35766,35767],{"class":129}," user.prefecture.name\n",[83,35769,35771],{"id":35770},"_3-ビジネスロジックの簡素化","3. ビジネスロジックの簡素化",[15,35773,35774],{},"データの変換処理がEntity層で完結するため、ビジネスロジックがシンプルになります。",[11,35776,21357],{"id":21357},[83,35778,35780],{"id":35779},"nullundefinedの扱い","Null\u002FUndefinedの扱い",[15,35782,35783],{},"データベースでNULL許可する場合は、Transformerでもそれを考慮してください。",[103,35785,35787],{"className":26556,"code":35786,"language":26558,"meta":108,"style":108},"@Column('tinyint', {\n  nullable: true,\n  transformer: {\n    from(id: number | null): IMaster | null {\n      return id ? Prefecture.findById(id) ?? null : null\n    },\n    to(value: IMaster | null): number | null {\n      return value?.id ?? null\n    },\n  },\n})\nprefecture: IMaster | null\n",[90,35788,35789,35801,35810,35815,35844,35869,35873,35902,35913,35917,35921,35925],{"__ignoreMap":108},[112,35790,35791,35793,35795,35797,35799],{"class":114,"line":115},[112,35792,9901],{"class":129},[112,35794,16182],{"class":163},[112,35796,425],{"class":129},[112,35798,34377],{"class":136},[112,35800,5239],{"class":129},[112,35802,35803,35806,35808],{"class":114,"line":122},[112,35804,35805],{"class":129},"  nullable: ",[112,35807,4345],{"class":156},[112,35809,179],{"class":129},[112,35811,35812],{"class":114,"line":143},[112,35813,35814],{"class":129},"  transformer: {\n",[112,35816,35817,35820,35822,35824,35826,35828,35830,35832,35834,35836,35838,35840,35842],{"class":114,"line":150},[112,35818,35819],{"class":163},"    from",[112,35821,425],{"class":129},[112,35823,17348],{"class":222},[112,35825,2243],{"class":125},[112,35827,17353],{"class":156},[112,35829,32224],{"class":125},[112,35831,3425],{"class":156},[112,35833,10186],{"class":129},[112,35835,2243],{"class":125},[112,35837,34893],{"class":163},[112,35839,32224],{"class":125},[112,35841,3425],{"class":156},[112,35843,1294],{"class":129},[112,35845,35846,35848,35851,35853,35855,35857,35860,35863,35865,35867],{"class":114,"line":170},[112,35847,5009],{"class":125},[112,35849,35850],{"class":129}," id ",[112,35852,23460],{"class":125},[112,35854,35466],{"class":129},[112,35856,35469],{"class":163},[112,35858,35859],{"class":129},"(id) ",[112,35861,35862],{"class":125},"??",[112,35864,3425],{"class":156},[112,35866,27955],{"class":125},[112,35868,32227],{"class":156},[112,35870,35871],{"class":114,"line":182},[112,35872,2378],{"class":129},[112,35874,35875,35878,35880,35882,35884,35886,35888,35890,35892,35894,35896,35898,35900],{"class":114,"line":193},[112,35876,35877],{"class":163},"    to",[112,35879,425],{"class":129},[112,35881,35490],{"class":222},[112,35883,2243],{"class":125},[112,35885,34893],{"class":163},[112,35887,32224],{"class":125},[112,35889,3425],{"class":156},[112,35891,10186],{"class":129},[112,35893,2243],{"class":125},[112,35895,17353],{"class":156},[112,35897,32224],{"class":125},[112,35899,3425],{"class":156},[112,35901,1294],{"class":129},[112,35903,35904,35906,35909,35911],{"class":114,"line":205},[112,35905,5009],{"class":125},[112,35907,35908],{"class":129}," value?.id ",[112,35910,35862],{"class":125},[112,35912,32227],{"class":156},[112,35914,35915],{"class":114,"line":241},[112,35916,2378],{"class":129},[112,35918,35919],{"class":114,"line":247},[112,35920,376],{"class":129},[112,35922,35923],{"class":114,"line":351},[112,35924,8436],{"class":129},[112,35926,35927,35930,35933,35935],{"class":114,"line":361},[112,35928,35929],{"class":163},"prefecture",[112,35931,35932],{"class":129},": IMaster ",[112,35934,34646],{"class":125},[112,35936,32227],{"class":156},[83,35938,35939],{"id":35939},"クエリビルダーでの使用",[15,35941,35942],{},"クエリビルダーを使う場合は、変換が自動で行われないため注意が必要です。",[103,35944,35946],{"className":26556,"code":35945,"language":26558,"meta":108,"style":108},"\u002F\u002F IDで検索する必要がある\nconst users = await userRepository\n  .createQueryBuilder('user')\n  .where('user.prefecture = :prefectureId', { prefectureId: 13 })\n  .getMany()\n",[90,35947,35948,35953,35966,35979,35998],{"__ignoreMap":108},[112,35949,35950],{"class":114,"line":115},[112,35951,35952],{"class":118},"\u002F\u002F IDで検索する必要がある\n",[112,35954,35955,35957,35959,35961,35963],{"class":114,"line":122},[112,35956,153],{"class":125},[112,35958,24024],{"class":156},[112,35960,160],{"class":125},[112,35962,419],{"class":125},[112,35964,35965],{"class":129}," userRepository\n",[112,35967,35968,35970,35973,35975,35977],{"class":114,"line":143},[112,35969,436],{"class":129},[112,35971,35972],{"class":163},"createQueryBuilder",[112,35974,425],{"class":129},[112,35976,32248],{"class":136},[112,35978,431],{"class":129},[112,35980,35981,35983,35986,35988,35991,35994,35996],{"class":114,"line":150},[112,35982,436],{"class":129},[112,35984,35985],{"class":163},"where",[112,35987,425],{"class":129},[112,35989,35990],{"class":136},"'user.prefecture = :prefectureId'",[112,35992,35993],{"class":129},", { prefectureId: ",[112,35995,35625],{"class":156},[112,35997,11585],{"class":129},[112,35999,36000,36002,36005],{"class":114,"line":170},[112,36001,436],{"class":129},[112,36003,36004],{"class":163},"getMany",[112,36006,630],{"class":129},[11,36008,919],{"id":919},[15,36010,36011],{},"TypeORMのTransformer機能を使うことで、次のメリットがあります。",[31,36013,36014,36017,36020],{},[34,36015,36016],{},"データベースとアプリケーション間のデータ変換を自動化",[34,36018,36019],{},"型安全性とコードの可読性が向上",[34,36021,36022],{},"マスターデータの扱いがシンプルに",[15,36024,36025],{},"マスターデータを多く扱うアプリケーションで特に有効な機能です。",[944,36027,36028],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":108,"searchDepth":122,"depth":122,"links":36030},[36031,36032,36037,36041,36046,36050],{"id":34859,"depth":122,"text":34859},{"id":34871,"depth":122,"text":34872,"children":36033},[36034,36035,36036],{"id":34875,"depth":143,"text":34876},{"id":35111,"depth":143,"text":35112},{"id":35283,"depth":143,"text":35284},{"id":35538,"depth":122,"text":35538,"children":36038},[36039,36040],{"id":35541,"depth":143,"text":35541},{"id":35587,"depth":143,"text":35587},{"id":35659,"depth":122,"text":35660,"children":36042},[36043,36044,36045],{"id":35663,"depth":143,"text":35664},{"id":35717,"depth":143,"text":35718},{"id":35770,"depth":143,"text":35771},{"id":21357,"depth":122,"text":21357,"children":36047},[36048,36049],{"id":35779,"depth":143,"text":35780},{"id":35939,"depth":143,"text":35939},{"id":919,"depth":122,"text":919},"TypeORMのTransformer機能を使って、データベースの数値IDとアプリケーションのマスターオブジェクトを相互変換する実装パターンを紹介します。",{"tags":36053},[107,26492,34847],"\u002Fblog\u002Ftypeorm-transformer",{"title":34854,"description":36051},"blog\u002Ftypeorm-transformer","vNzBjT0VN3k3JScjaVJ-20JE2FMZacMOg3dir-pZH00",1773664053531]