App Engineを試す vol4 ~サービス同士を連携させる~
概要
AppEngine勉強日記、4日目。
この記事では、default
サービス上のNuxtからexpressを経てapis
サービスのAPIにアクセスするところを検証します。
すごい、4日連続で続いたよ。アドベントカレンダーいけんじゃね?
背景
AppEngineが便利っていう話を聞いたので、AppEngineでどこまでできるのかを試す連載。 連載のゴールとしては、NuxtをBFFとしておいてAPIで処理を行うというマルチサービスの構成。
作るサービスの仕様
- 題名と本文を投稿できる
- 投稿すると、題名と本文を閲覧できるページに遷移する
以上。いわゆるnopasteと呼ばれるサービスです。slackの時代、もう知らないひともいるんじゃなかろうか。
あ、コードやテストについては、日付更新まで2hしかなかったのでとりあえずなるはやの全力で書いたので結構雑です。 そのうちリファクタする。
defaultサービスの変更
通信用にaxiosをとりあえず入れる。
> cd root > npm i axios
ページの作成
ついでにlayoutも作るけど省略。
./root/pages/index.vue
投稿用の画面を作る。
title
とbody
を入力して、post
を押下すると/apis/posts
にPOST
リクエストを投げる。
<template> <form class="form"> <div class="form-group"> <label for="title" class="form-label">Title</label> <div> <input type="text" id="title" class="form-control" v-model="input.title"> </div> </div> <div class="form-group"> <label for="body" class="form-label">Body</label> <div> <textarea id="body" rows="10" v-model="input.body"></textarea> </div> </div> <div class="form-group"> <button type="button" class="btn-primary" :disabled="!is_valid" @click="submit">Post</button> </div> </form> </template> <script> import axios from 'axios' export default { data() { return { input: { title: null, body: null } } }, methods: { is_valid() { return this.body }, async submit() { const { data } = await axios.post('/apis/posts', { title: this.input.title, body: this.input.body }) this.$router.push(`/posts/${data.id}`) } } } </script>
pages/posts/_id.vue
閲覧画面の作成。
/apis/posts/:id
から文書情報を取得して表示する。
<template> <div v-if="content"> <h2>{{content.title || 'no title'}}</h2> <div> {{content.body}} </div> </div> </template> <script> import axios from 'axios' export default { data() { return { content: null } }, mounted() { axios.get(`/apis/posts/${this.$route.params.id}`) .then(({ data }) => { this.content = data }) } } </script>
expressサーバにAPIを追加
POSTリクエストを受け取れるようにbodyParserをセット。
GET /apis/posts/:id
と POST /apis/posts
のルーティングを作成。
apisサービスのAPIを立たけるようにする。
このとき、apisサービスのオリジンは環境変数から渡せるようにする。
diff --git a/root/app.js b/root/app.js index 35a950f..c9120af 100644 --- a/root/app.js +++ b/root/app.js @@ -5,17 +5,34 @@ const express = require('express'); const app = express(); const { Nuxt, Builder } = require('nuxt') +app.use(express.json()) +app.use(express.urlencoded({ extended: true })); + // Nuxt.js をオプションとともにインスタンス化する const config = require('./nuxt.config') config.dev = process.env.NODE_ENV !== 'production' const nuxt = new Nuxt(config) -app.get('/message', (req, res) => { - res - .status(200) - .send('Hello, world!') - .end(); -}); +const axios = require('axios') +app.post('/apis/posts', (req, res) => { + const data = req.body + axios.post(`${process.env.APIS_ORIGIN}/posts`, { + title: data.title, + body: data.body + }) + .then(({ data }) => { + res.json({ id: data.id }) + }) +}) +app.get('/apis/posts/:id', (req, res) => { + axios.get(`${process.env.APIS_ORIGIN}/posts/${req.params.id}`) + .then(({ data }) => { + res.json(data) + }) + .catch(({ response }) => { + res.status(response.status).json(response.body) + }) +}) app.use(nuxt.render)
app.yamlにapisサービスのオリジンを設定
diff --git a/root/app.yaml b/root/app.yaml index eed1e47..6c4cf07 100644 --- a/root/app.yaml +++ b/root/app.yaml @@ -1 +1,4 @@ runtime: nodejs10 + +env_variables: + APIS_ORIGIN: https://apis-dot-astral-web-260712.appspot.com
deploy
> npm run build > gcloud app deploy
これだけではまだ見れないので、続けてapisサービスを作っていく
apisサービスの変更
GET /posts/:id
にリクエストがきたらサービスから任意のIDのレコードを取得して返却。
POST /posts
にリクエストが来たらサービスにtitle
とbody
を渡してid
を返却。
import { Controller, Post, Body, Get, Param, NotFoundException } from '@nestjs/common'; import { PostsService } from './posts.service'; @Controller('posts') export class PostsController { constructor(private readonly postService: PostsService) { } @Get(':id') async fetchById(@Param('id') id: string): Promise<{id: number, title: null|string, body: string}> { const row = await this.postService.fetchById(Number(id)); if (!row) { throw new NotFoundException(); } return row; } @Post('') async save( @Body('title') title: null|string, @Body('body') body: string, ): Promise<{ id: number }> { return await this.postService.save({ title, body }); } }
サービスを定義する。面倒なのでメモリ上にレコードを保持する。
import { Injectable } from '@nestjs/common'; @Injectable() export class PostsService { private rows: Array<{ id: number, title: string|null, body: string }>; constructor() { this.rows = []; } async fetchById(id: number) { return this.rows.find((row) => row.id === id); } async save({ title, body }: { title: string|null, body: string }): Promise<{ id: number }> { const id = this.rows.length + 1; this.rows.push({ id, title, body }); return { id }; } }
PostsModule
を定義して、ControllerとServiceを関連付ける。
import { Module } from '@nestjs/common'; import { PostsController } from './posts.controller'; import { PostsService } from './posts.service'; @Module({ imports: [], controllers: [PostsController], providers: [PostsService], }) export class PostsModule {}
最後にAppModuleにPostsModuleを追加して完了
diff --git a/apis/src/app.module.ts b/apis/src/app.module.ts index 8662803..5c11c51 100644 --- a/apis/src/app.module.ts +++ b/apis/src/app.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { PostsModule } from './posts/posts.module'; @Module({ - imports: [], + imports: [PostsModule], controllers: [AppController], providers: [AppService], })
deployする
> npm run build > gcloud app deploy
動作を確認する
gcloud app browse -s default
すると投稿画面が開く。
値を入力してPostすると表示ページに遷移する。
できたー!
まとめ
なんとか4日目の対応を乗り切ったぜ...!
ここまで勧めてみたけど、いくつか気になる点がある。
今後の課題にしていきます!
次回予告
というわけで、明日はデータストア周りさわってみたいなー!