- 投稿日:2020-05-28T23:47:39+09:00
Swagger-nodeのSwagger定義ファイルを自動的にマージさせる
Swagger-nodeを使って、よくRESTfulサーバを立ち上げるのですが、エンドポイントの追加作業が煩雑になりがちです。
普段はこんな感じで、エンドポイントを追加しているのではないでしょうか。
・既存のapi/swagger/swagger.yamlにエンドポイントを追加
・各エンドポイントに以下を追加
x-swagger-router-controller: ソースファイル名
・各メソッドに以下を追加
operationId: オペレーションID
・ソースファイルに、以下のexportsを追記module.exports = { オペレーションID: 関数名 };上記の作業において、課題がいくつかあります。
・swagger.yaml に、複数のエンドポイントの定義が混在しているためメンテナンスしづらい
・ソースファイルを置いているフォルダ(api/controllers/)に、複数のエンドポイントのソースファイルが混在し散らかる。そこで、以下をやってくれるユーティリティを作ろうと思います。
<準備>
・api/contollers/フォルダ配下にフォルダを作成し、エンドポイントのソースファイルを配置する。複数のエンドポイントの実装を1つのソースファイルにまとめてもよいです。
・そのフォルダに「swagger.yaml」を作成し、それらエンドポイントを定義する。<操作>
> npm run automount
を実行すると、api/contollers/フォルダ配下の各フォルダにswagger.yamlがある場合、その内容をapi/swagger/swagger.yamlファイルに自動マージする。
> npm run autoclean
を実行すると、自動マージしていた定義をapi/swagger/swagger.yamlファイルから削除する。どうでしょうか、フォルダごとにエンドポイントの実装もSwagger定義ファイルもまとめられます。
例えばこんな感じです。まずは自動マージ前です。
api/swagger/swagger.yamlswagger: "2.0" info: version: "0.0.1" title: Hello World App host: localhost:10010 basePath: / schemes: - http - https consumes: - application/json produces: - application/json paths: /swagger: x-swagger-pipe: swagger_raw /hello: x-swagger-router-controller: hello_world get: operationId: hello parameters: - name: name in: query type: string responses: "200": description: Success schema: $ref: "#/definitions/HelloWorldResponse" definitions: HelloWorldResponse: required: - message properties: message: type: stringこのままでも、api/controllers/hello_world.jsを呼び出せます。
api/controllers/フォルダに以下のフォルダおよびファイルを作成します。
api/controllers/folder1/
index.js
swagger.yamlapi/controllers/folder2/index.js
index.js
swagger.yaml各ファイルの内容です。
api/controllers/folder1/swagger.yamlpaths: /test1: get: description: Returns 'Hello' to the caller # used as the method name of the controller operationId: test1_id parameters: - name: name in: query description: The name of the person to whom to say hello required: false type: string responses: "200": description: Success schema: # a pointer to a definition $ref: "#/definitions/HelloWorldResponse" # responses may fall through to errors default: description: Error schema: $ref: "#/definitions/ErrorResponse"api/controllers/folder1/index.js'use strict'; const util = require('util'); module.exports = { test1_id: test }; function test(req, res) { // variables defined in the Swagger document can be referenced using req.swagger.params.{parameter_name} var name = req.swagger.params.name.value || 'stranger'; var hello = util.format('Hello, %s!', name); // this sends back a JSON response which is a single string res.json(hello); }folder1にオペレーションID:test1_idが定義されているのがわかります。
同様に、folder2にオペレーションID:test2_idが定義しています。この状態で、以下を実行します。
> npm run automount
そうすると、api/swagger/swagger.yamlが以下のように再構成されます。
api/swagger/swagger.yamlswagger: "2.0" info: version: "0.0.1" title: Hello World App host: localhost:10010 basePath: / schemes: - http - https consumes: - application/json produces: - application/json paths: /swagger: x-swagger-pipe: swagger_raw /hello: x-swagger-router-controller: hello_world get: operationId: hello parameters: - name: name in: query type: string responses: "200": description: Success schema: $ref: "#/definitions/HelloWorldResponse" /test1: # automounted get: description: Returns 'Hello' to the caller # used as the method name of the controller operationId: test1_id parameters: - name: name in: query description: The name of the person to whom to say hello required: false type: string responses: "200": description: Success schema: # a pointer to a definition $ref: "#/definitions/HelloWorldResponse" # responses may fall through to errors default: description: Error schema: $ref: "#/definitions/ErrorResponse" x-swagger-router-controller: routing x-automount: folder1 /test2: # automounted get: description: Returns 'Hello' to the caller # used as the method name of the controller operationId: test2_id parameters: - name: name in: query description: The name of the person to whom to say hello required: false type: string responses: "200": description: Success schema: # a pointer to a definition $ref: "#/definitions/HelloWorldResponse" # responses may fall through to errors default: description: Error schema: $ref: "#/definitions/ErrorResponse" x-swagger-router-controller: routing x-automount: folder2 definitions: HelloWorldResponse: required: - message properties: message: type: string各フォルダfolder1とfolder2にあるswagger.yamlがマージされているがわかりますでしょうか。
その際に、以下の2つを自動的に付けています。x-swagger-router-controller: routing x-automount: folder1これらは後でからくりを示しますが、いったんrouting.jsに振り向けた後、各フォルダfolder1またはfolder2のindex.jsに転送しています。
あと、わかりやすいように、エンドポイントのところにコメントで
# automounted
と付けておきました。Swagger-nodeへの組み込み
それではさっそく上記機能をSwagger-nodeに組み込んでみます。
まずは普通に、Swaggerプロジェクトを作成します。
> swagger project create プロジェクト名
プロジェクト名は何でも良いです。
途中、フレームワークを聞かれますが、expressを選択します。次に、api/controllers/routing.jsを作成しましょう。
routing.js'use strict'; const fs = require('fs'); var func_auto_table = []; const files = fs.readdirSync("api/controllers"); for( var i = 0 ; i < files.length ; i++ ){ var stats_dir = fs.statSync("api/controllers/" + files[i]); if( !stats_dir.isDirectory() ) continue; try{ fs.statSync("api/controllers/" + files[i] + "/swagger.yaml" ); }catch(error){ continue; } func_auto_table[files[i]] = require('./' + files[i]); console.log('auto mouted: ' + files[i]); }; var exports_list = {}; for( var operationId in func_auto_table ){ Object.keys(func_auto_table[operationId]).forEach(item =>{ exports_list[item] = routing; }); } module.exports = exports_list; function routing(req, res) { var operationId = req.swagger.operation.operationId; return func_auto_table[req.swagger.operation["x-automount"]][operationId](req, res); }次に、ルートにautomount.jsを作成しましょう。
automount.js'use strict'; const yaml = require('yaml'); const yaml_types = require('yaml/types'); const fs = require('fs'); const SWAGGER_FILE = "api/swagger/swagger.yaml"; const TARGET_FNAME = "swagger.yaml"; const ROUTING_NAME = "routing"; const root_file = fs.readFileSync(SWAGGER_FILE, 'utf-8'); const root = yaml.parseDocument(root_file); if( process.argv.length != 3){ console.error('Invalid Params'); return; } if( process.argv[2] == 'clean'){ var num = 0; num += delete_paths(root); num += delete_definitions(root); if( num == 0 ){ console.log(SWAGGER_FILE + ' no changed'); return; } var swagger = String(root); fs.writeFileSync(SWAGGER_FILE, swagger, 'utf-8'); console.log(SWAGGER_FILE + ' cleaned'); }else if( process.argv[2] == 'mount'){ var num = 0; num += delete_paths(root); num += delete_definitions(root); const files = fs.readdirSync("api/controllers"); for( var i = 0 ; i < files.length ; i++ ){ var stats_dir = fs.statSync("api/controllers/" + files[i]); if( !stats_dir.isDirectory() ) continue; try{ fs.statSync("api/controllers/" + files[i] + '/' + TARGET_FNAME ); }catch(error){ continue; } const file = fs.readFileSync("api/controllers/" + files[i] + '/' + TARGET_FNAME, 'utf-8'); const doc = yaml.parseDocument(file); num += append_paths(root, doc, files[i]); num += append_definitions(root, doc, files[i]); }; if( num == 0 ){ console.log(SWAGGER_FILE + ' no changed'); return; } var swagger = String(root); fs.writeFileSync(SWAGGER_FILE, swagger, 'utf-8'); console.log(SWAGGER_FILE + ' mounted'); }else{ console.error('Invalid Params'); return; } return; function append_paths(root, target, name){ var map = target.get('paths'); if( !map ) return 0; var num = 0; for( var i = 0 ; i < map.items.length ; i++ ){ if( map.items[i].value.items[0].value instanceof yaml_types.Scalar ) continue; var children = map.items[i].value.items; for( var j = 0 ; j < children.length ; j++ ){ children[j].value.set("x-swagger-router-controller", ROUTING_NAME); children[j].value.set("x-automount", name); } map.items[i].comment = " automounted"; root.addIn(['paths'], map.items[i]); console.log('mounted(paths): ' + map.items[i].key.value); num++; } return num; } function append_definitions(root, target, name){ var map = target.get('definitions'); if( !map ) return 0; for( var i = 0 ; i < map.items.length ; i++ ){ map.items[i].value.set("x-automount", name); map.items[i].comment = " automounted"; root.addIn(['definitions'], map.items[i]); console.log('mounted(definition): ' + map.items[i].key.value); } return map.items.length; } function delete_paths(root){ var map = root.get('paths'); if( !map ) return 0; var delete_target = []; for( var i = 0 ; i < map.items.length ; i++ ){ if( map.items[i].value.items[0].value instanceof yaml_types.Scalar ) continue; var children = map.items[i].value.items; for( var j = 0 ; j < children.length ; j++ ){ if( children[j].value.get("x-automount") ){ delete_target.push(map.items[i].key.value); break; } } } for( var i = 0 ; i < delete_target.length ; i++ ) map.delete(delete_target[i]); return delete_target.length; } function delete_definitions(root){ var map = root.get('definitions'); if( !map ) return 0; var delete_target = []; for( var i = 0 ; i < map.items.length ; i++ ){ if( map.items[i].value.get("x-automount") ) delete_target.push(map.items[i].key.value); } for( var i = 0 ; i < delete_target.length ; i++ ) map.delete(delete_target[i]); return delete_target.length; }npmモジュール yamlを使ってSwaggerファイルの参照および編集を行っています。
yamlパッケージを追加しましょう。
> npm install yaml
package.jsonのscriptsに、以下を追加しましょう。
"automount": "node automount.js mount", "autoclean": "node automount.js clean",これで準備完了です。
とりあえず、以下で起動できます。
> npm run start
または
> node app.js
コンソールに表示されている通り、
http://127.0.0.1:10010/hello?name=Scott
にアクセスするとレスポンスが返ってきます。それでは、次に先ほど示したfolder1フォルダとその配下のファイルindex.jsとswagger.yamlをapi/controllers/フォルダに配置します。
そして以下を実行します。
> npm run automount
> npm run start
以下のURLにアクセスして、ちゃんと応答が返ってきたら成功です。
http://127.0.0.1:10010/test1?name=Scott
完成品
以下のGitHubに置いておきました。
poruruba/swagger_automount
https://github.com/poruruba/swagger_automountこちらもご参考にどうぞ
SwaggerでRESTful環境を構築する以上
- 投稿日:2020-05-28T16:33:55+09:00
ヒープ領域制限によりビルドが通らない場合
環境
インスタンス:t3a.nano
OS: AmazonLinux2
node.js:v14.3.0上記環境でnpm run serveを実施してVueアプリの動作確認をした後に
npm run buildでビルドを実行したところFATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memoryのエラーが表示されてビルドが通らなかった。
ビルドするためのメモリが足らないと判断できるため
スワップ領域を確保したうえで
https://qiita.com/nakamto/items/5e78e9caceeff6b9e2b4$ export NODE_OPTIONS="--max-old-space-size=1024" $ npm run build※1024は確保できるメモリ量に応じて変更してください。
max-old-space-sizeの値を指定することでビルドが通りました。メモリの少ないインスタンスを利用していることが原因
と推測しますので、メモリが大きいインスタンスへ変更しても良いかもしれません。
- 投稿日:2020-05-28T13:13:41+09:00
ビデオチャット中に左シフトキーを押している間だけミュート解除し会話できるアプリをつくった
Google MeetやZOOMでビデオチャットをしているとき、マイクに雑音が入らないよう自分の発言中以外は基本的にミュートしているのですが、
ミュート解除を切り忘れたり、別画面を開いているとすぐにミュート解除できなかったりで、面倒だったのでキーボードの特定のキーを押している間だけミュートを解除してくれるアプリ「Cough Switch」を作成しました。トランシーバーやレコーディングスタジオのトークバック機能のようなイメージです。
AppleScriptでマイクの音量を操作する
Macの場合、
システム環境設定 > サウンド > 入力
でマイク設定を操作することができます。
この入力音量を0にするとミュートされます。applescripttell application "System Events" to set volume input volume 100このスクリプトをjsから実行するにはこんな感じ
javascriptconst applescript = require('applescript'); applescript.execString('tell application "System Events" to set volume input volume 100', function);
iohook
ですべてのキーボードイベントを取得するさくっと作りたいのでまたElectronで簡単にアプリ化していきます。
Webのいつも通りにキーボード操作を取得しようと、
document.addEventListener("keydown", function)
としてしまうと、Windowがアクティブになっていないとキーボードイベントを取得できません。
electron.globalShortcut
ではkeyup
イベントを取得できないので却下。他に探してみたところiohookってやつで取得できるみたいです。
(特定のバージョンのNodeとElectronしかサポートしていないらしくちょっとはまった)javascriptconst iohook = require('iohook'); const KEYCODE = 42; // left shift key iohook.on('keydown', (msg) => { if (msg.keycode === KEYCODE) { talk(); } }); iohook.on('keyup', (msg) => { if (msg.keycode === KEYCODE) { mute(); } });どのキーに割り当てるか悩みましたが2つあるので、左シフトキーにしました。
Electronの最前面の触れない透明なWindowを作成
ミュート解除中のアイコンを表示させるために、最前面に触れないWindowを作成します。
さらに邪魔にならないよう背景を透明にします。javascriptconst appWindow = new BrowserWindow({ transparent: true, frame: false, resizable: false, alwaysOnTop: true, }); appWindow.setIgnoreMouseEvents(true); appWindow.loadURL(`file://${__dirname}/index.html`);これで最前面にHTMLが表示されます。
あとはキーボードイベントに応じて、Windowへ状態を送り、CSSでアイコンを表示させます。javascriptappWindow.webContents.send('talk');おわり
あとは前にやった MacBook Proの充電器の情報をメニューバーに表示するElectronアプリをつくった と同様にアプリケーション化して完成です。
ソースコードはこちら(https://github.com/narikei/cough-switch)
おわりのおわり
デフォルトだとスピーカーの設定しかできないけど、
こういうときこそタッチバーの出番なのではないかな?
バックグラウンドのアプリでタッチバーを使えないのどうにかしてほしい。
- 投稿日:2020-05-28T06:53:09+09:00
Puppeteerを2系から3系にアップデートしたらError: Failed to launch the browser process!
Puppeteerメモです。
Heroku上で利用していたPuppetterのバージョンを2系->3系にアップデートしたらエラー発生。
Puppetterのバージョンを2系->3系にするとChromiumのバージョン違いで動かないっぽい
Heroku上のログ
Error: Failed to launch the browser process! 2020-05-27T21:22:10.217234+00:00 app[web.1]: /app/node_modules/puppeteer/.local-chromium/linux-756035/chrome-linux/chrome: error while loading shared libraries: libgbm.so.1: cannot open shared object file: No such file or directoryHeroku上でインストールされているChromiumを更新する
Puppetterのv3.1.0ではChromium 83.0.4103.0 (r756035)を利用していると、Big changesと書かれていて、内部で利用するChromiumのバージョンが違う模様です。
最初にインストールしたときはPuppetterのv2系だったので、その時に対応していたバージョンのChromiumがHeroku側に残ったままになってるんだと思います。
bildpackを再ビルド
$ heroku buildpacks:add jontewks/puppeteerその後デプロイしなおす(
git push heroku master
)と再度ビルドされてHeroku上のChromiumも更新されてこれで問題なく利用できるようになりました。その他メモ
その他触っててでたエラーをメモ的に残しておきます。
TimeoutError: Navigation timeout of 30000 ms exceeded
タイムアウト。
デフォルトだと30秒待ってくれるみたいだけど、それ以上かかる場合もありそう。
page.goto()
のオプションで{waitUntil: 'networkidle'}
を指定するっぽいけど更にエラーがError: ERROR: "networkidle" option is no longer supported. Use "networkidle2" insteadなるほど
page.goto(url, {waitUntil: 'networkidle2'})でうまくいった。
Error: Evaluation failed: TypeError: Cannot set property 'value' of null
こんな雰囲気でiPhoneでのページ閲覧エミュレートをしてましたが、
const devices = require('puppeteer/DeviceDescriptors'); const iPhone = devices['iPhone X']; 省略 await page.emulate(iPhone);これもv2->3にあげたら
Error: Evaluation failed: TypeError: Cannot set property 'value' of null
とエラーが発生し、うまく動作しなくなってしまいました。これパッと調べても原因や解決策がわからなかったので、iPhoneでのエミュレートを泣く泣く断念。
今回やりたかったのはデスクトップエミュレートでもなんとかなったのでよかったけど、必要になったらまた調べないと...
- 投稿日:2020-05-28T02:00:10+09:00
Vue.jsでAPIのBasic認証をした
はじめに
Vue.jsによる開発においてAPIのBasic認証の実装をしました。
エラーについての検索をした際に類似のケースで苦しんでいる方が国内外問わず多くいらっしゃったので、本記事では私が嵌ったエラーと解決策の一例について書いていきます。
当記事の使用言語はVue.jsとNode.jsです。課題の切り分け
実装に当たって私が悩んだ点は大きく分けて下記二つとなります。
・CORSの不一致
・非同期処理の中でBasic認証が出来ない(404エラーが返ってくる)◎CORSの不一致
APIのBase URLに対してGETでデータの取得を初めて求めた際に、
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.以上のエラーが返ってきた。
エラーはどうやら「same-origin」と呼ばれるセキュリティポリシーにより、ブラウザが同一のドメインを持つアプリケーションのみと対話するように保護されているためであり、
Google先生に質問をしたところ、VueではProxyの設定でCORSのエラーを回避出来ることが分かり、configを設定するファイルに下記のような記述をした。
vue.config.jsmodule.exports = { devServer : { // proxy all requests starting with /api to jsonplaceholder "/testapi": { target: "https://api.test.hogehoge.jp/", changeOrigin: true, pathRewrite: { "^/testapi": "" } } }公式「フロントエンドアプリとバックエンドAPIサーバーが同じホスト上で実行されていない場合は、開発中にAPIリクエストをAPIサーバーにプロキシする必要がある」・・・。
改めてドキュメントを読み込む大切さを思い知った・・・?ちなみにCORSについてはこちらの記事が大変分かりやすかったです、ありがとうございます!
◎非同期処理の中でBasic認証が出来ない(404エラーが返ってくる)
CORSの問題を解決して一件落着!と思いきや、GETして拾ってきたのはJsonではなく404エラーだった。
エラーはBasic認証の部分で発生しており、axiosを利用した非同期処理のロジックを組みながら最終的に下記のようなコードで実装をした。
getApiData.jsgetApiData({ commit }) { const clientId = "hoge"; const clientSecret = "test"; //Basic認証のために、base64でエンコードする const encodedData = Buffer.from(clientId + ":" + clientSecret).toString( "base64" ); axios .get("/testapi", { withCredentials: true, headers: { Authorization: "Basic " + encodedData } }) .then(function(response) { console.log("成功"); }) .catch(function(error) { console.log("失敗"); }); }これは定型文的に決まってルールとして、
Basic認証がかかったURLにアクセスする場合にはAuthorizationヘッダーへ向けて、Authorization: Basic <Base64エンコードしたユーザ名:パスワード>以上をリクエストをする必要があるようだった。
おわりに
今回APIを扱うに当たって、はじめてポストマン を使ってみたところ、素早くテストをすることが出来ました。学習コストはほとんどなく、直感的に扱えたので次回以降も継続して利用をしていこうと思います。
- 投稿日:2020-05-28T01:48:29+09:00
新人に負けない本棚管理サイト その4(データベース検索編①)
目次
新人に負けない本棚管理サイト その1(プロローグ)
新人に負けない本棚管理サイト その2(環境構築編)
新人に負けない本棚管理サイト その3(トップページ作成編)
新人に負けない本棚管理サイト その4(データベース検索編①)目標
- データベースに問い合わせしてデータを取得する
- 取得したデータを画面に表示する ←今回はここまで
- 検索できるようにする
データの準備
使用するデータベースはMariaDBです。
セットアップ方法はその2を参照してください。テーブル作成
CREATE_TABLE_Books.sqlCREATE TABLE Books( title VARCHAR(100) primary key ,author VARCHAR(20) ,publisher VARCHAR(20) ,finished__at DATE DEFAULT CURDATE() )仮データ登録
CREATE_TABLE_Books.sqlINSERT INTO Books VALUES ('Python Django超入門', '掌田津耶乃', '秀和システム', '2019/08/31'), ('React.js&Next.js超入門', '掌田津耶乃', '秀和システム', NULL), ('PostgreSQL徹底入門 第4版', '近藤雄太 他', '翔泳社', NULL), ('PHPフレームワークLaravel入門 第2版', '掌田津耶乃', '秀和システム', '2019/10/31'), ('PostgreSQLから始めるデータベース生活', 'まぐろ', 'まぐろのみぞおち', '2019/11/03')一緒にSearchResultComponentで仮に入れていた本データを削除します。
import React from 'react'; import { Container, Row, Col, Table } from 'react-bootstrap'; export default class SearchResult extends React.Component { render() { return ( <Container> <Row> <Table striped bordered size="sm"> <thead> <tr> <th>タイトル</th> <th>著者名</th> <th>出版社</th> <th>読了日</th> </tr> </thead> <tbody> {/* 削除 <tr> <td>Python Django超入門</td> <td>掌田津耶乃</td> <td>秀和システム</td> <td>2019/08/31</td> </tr> ... <tr> <td>PostgreSQLから始めるデータベース生活</td> <td>まぐろ</td> <td>まぐろのみぞおち</td> <td>2019/11/03</td> </tr> */} </tbody> </Table> </Row> </Container> ); } }スタートアッププログラムを変更
Expressのインストール
リクエストの処理を定義するために、Node.jsのフレームワークのExpressをインストールします。
terminalnpm install express
スタートアッププログラム作成
まずプロジェクト直下に
server.js
を作ります。
引用:server.jsconst express = require('express') const next = require('next') const dev = process.env.NODE_ENV !== 'production' const app = next({ dev }) const handle = app.getRequestHandler() app.prepare() .then(() => { const server = express(); server.get('*', (req, res) => { return handle(req, res); }) server.listen(3000, (err) => { if(err) { throw err } console.log('> Ready on http://localhost:3000') }) }) .catch((ex) => { console.error(ex.stack) process.exit(1) })次にpackage.jsonのscript部を書き換えます。
"scripts": { "dev": "node server.js", "build": "next build", "start": "NODE_ENV=production node server.js" }そして再度
npm run dev
すると、ターミナルに> Ready on http://localhost:3000
が表示されると思います。
この段階で(開発用の)Webサーバーが立ち上がったということになります。404を解決する
今のままでは、/bookmanagerにアクセスしたあとリロードすると404が表示されます。
これはserver.jsで/bookmanager
にアクセスしたときの処理内容を定義していないため、どのページを表示させるか分からなかったためです。じゃあ定義しよう!!
server.jsに以下のように追記します。
server.js// (略) app.prepare() .then(() => { const server = express(); // 追記ここから server.get('/bookmanager', (req, res) => { return app.render(req, res, '/bookmanager', req.query); }); // 追記ここまで server.get('*', (req, res) => { return handle(req, res); }) server.listen(3000, (err) => { if(err) { throw err } console.log('> Ready on http://localhost:3000') }) }) .catch((ex) => { console.error(ex.stack) process.exit(1) })これにより、
/bookmanager
にgetリクエストが来たらpages/bookmanager/index.js
を表示させるという定義ができました。
何度リロードしても404になりません!!データベースへ問い合わせ
最初にページを開いたときに、データベースから本情報を全件取ってきて表示させます。
メインの内容です!!MariaDBへ接続するためのモジュールを追加
terminalnpm install mariadb
データベース接続情報を定義する
プロジェクト直下に
mariadbConnection.js
を作ります。mariadbConnection.jsconst mariadb = require('mariadb/callback'); const dbConfig = { host: '192.168.xxx.xxx', user: 'username', password: 'yourpassword', database: 'database-name' }; const connection = mariadb.createConnection(dbConfig); module.exports = connection;データベースからデータ取得
bookmanagerページを開いたときにコンソールにデータを表示させてみましょう。
server.jsを書き換えます。server.js// (略) app.prepare() .then(() => { const server = express(); // 変更ここから const connection = require('./mariadbConnection'); server.get('/bookmanager', (req, res) => { let query = "SELECT title, author, publisher, DATE_FORMAT(finished_at, '%Y/%m/%d') AS finished_at FROM Books ORDER BY title"; connection.query(query, (err, rows) => { if(err) throw err; console.log(rows); }); return app.render(req, res, '/bookmanager', req.query); }); // 変更ここまで server.get('*', (req, res) => { return handle(req, res); }) server.listen(3000, (err) => { if(err) { throw err } console.log('> Ready on http://localhost:3000') }) }) .catch((ex) => { console.error(ex.stack) process.exit(1) })
npm run dev
してWebサーバーを再起動します。
その後bookmanagerページを開き、ターミナルを見てみると...いいっすねぇ~(・∀・)
取得結果を画面にバインド(やることが多いです)
通常のWebアプリでは、以下のように検索する仕組みを作ると思います。
- 検索ボタンを押したとき、入力値をサーバーへ送る
- サーバーサイドのプログラムからデータベースへ、検索クエリを投げる
- 結果を画面へ返す
- 画面でイイカンジに表示する
つまり検索するたびにサーバーへリクエストが飛ぶため、ヒット件数が多かったりストアドプロシージャ/ファンクションを介したりする場合はそれなりに時間がかかります。
せっかくNextというエクストリームニュージェネレーションフレームワーク(言いたいだけ)を使っているのに、こんなクラシックなやり方で応答時間を増やすのはダメです!そこで、
「最初にbookmanagerページを開いたときに一度だけ全件取得して、検索するときはそのデータから絞り込む」
ということをしてみます。
これならデータベースへの問い合わせは初めてページを開くときの一度のみになるので、圧倒的にレスポンスが短くなります。(ページをリロードしたときも初めて開いたとする)ページオープン時に全件取得
NextにはgetInitialPropsという便利な関数があります。
カンタンに言うと「ページが表示されたとき一度だけ実行される非同期関数」って感じです。
やりたいことにもってこいの関数ですね。
この中でデータベースへ問い合わせをします。まずbookmanagerページにgetInitialProps関数を追加します。
pages/bookmanager/index.jsimport Layout from '../../components/layout'; import React from 'react'; import { Container, Row, Col, Table, Form } from 'react-bootstrap'; export default class Index extends React.Component { // 追記ここから static async getInitialProps() { const res = await fetch('http://192.168.130.69:3000/bookmanager/get'); const allBooks = await res.json(); return { allBooks }; } // 追記ここまで render() { {/* 略 */} }getInitialPropsの中でAPIを呼び結果を取得しています。
この呼び出しの定義をserver.jsで記述します。server.jsapp.prepare().then(() => { const connection = require('./mariadbConnection'); // データベース呼び出し処理は削除してください。 server.get('/bookmanager', (req, res) => { /* let query = "SELECT title, author, publisher, DATE_FORMAT(finished_at, '%Y/%m/%d') AS finished_at FROM Books ORDER BY title"; connection.query(query, (err, rows) => { if(err) throw err; console.log(rows); }); */ return app.render(req, res, '/bookmanager', req.query); }); // 追記ここから server.get('/bookmanager/get', async (req, res) => { let query = "SELECT title, author, publisher, DATE_FORMAT(finished_at, '%Y/%m/%d') AS finished_at FROM Books ORDER BY title"; connection.query(query, (err, rows) => { if(err) throw err; res.json(rows); // レスポンスに取得データを持たせる }); }); // 追記ここまで server.get('*', (req, res) => { return handle(req, res); }); server.listen(3000, (err) => { if(err) { throw err; } console.log('> Ready on http://localhost:3000'); }); }) .catch((ex) => { console.error(ex.stack); process.exit(1); });毎度のことながら、
npm run dev
してアクセスします。
データベースには以下のデータが入っています。
title author publisher finisher_at PHPフレームワークLaravel入門 第2版 掌田津耶乃 秀和システム 2019/10/31 PostgreSQLから始めるデータベース生活 まぐろ まぐろのみぞおち 2019/11/03 PostgreSQL徹底入門 第4版 近藤雄太 他 翔泳社 NULL Python Django超入門 掌田津耶乃 秀和システム 2019/08/31 React.js&Next.js超入門 掌田津耶乃 秀和システム NULL プリンをもっと見守る技術 M5StickVで体験するAIの世界 aNo研 aNo研 2020/05/26 アクセスすると...
ちゃんと表示されました(;^ω^)
お疲れ様でした
盛りだくさんで書くのも大変でした。
日本語を書くのがへたっぴなので、分かりづらいところがあると思います。
小さい目標を1つずつ達成して、
「現段階で何ができているのか」「次に何ができてほしいか」「ゴールまで到達するにはあと何ができなければならないか」
をまとめるようにすると、ワカラナイパニックに陥ることは少ないと思います。と、私の会社の先輩が言っていました。
次回
条件を入力して検索できるようにします。
- 投稿日:2020-05-28T01:07:15+09:00
Vue.jsでつくったサイトにfirebaseでユーザ認証してこっそり人の顔年齢を試して遊ぶ
概要
Vue.jsで作ったサイトをFirebaseにユーザー認証をお願いして、ログインしないと入れないページにして、人の顔年齢(Computer Vision API)で遊んで、freenomとNetlifyで独自ドメインで公開する
できたもの
環境
macOS Catalina Visual Studio Code 1.45.1 Node.js: v13.11.0 npm:6.14.5 Vue:@vue/cli 4.3.1大まかな流れ
Vue.jsとFirebaseでユーザー認証ありのプロジェクトをつくる
↓
Computer Vision APIをつかって顔認識のぺーじをつくる
↓
freenomでドメインを取る
↓
Netlifyに freenomドメインをあててDeployメインの構成はこんな感じです
App.vue main.js assets components - Signup.vue - HelloWorld.vue - Signin.vue router - index.jsVue.jsとFirebaseでユーザー認証ありのプロジェクトをつくる
こちらがめちゃくちゃわかりやすかったです。
こちらを参考にVue.jsとFirebaseでユーザー認証ありのプロジェクトをつくるVue.js + Firebase を使って爆速でユーザ認証を実装する - Qiita
src/components/Signin.vue<template> <div class="signin"> <h2>Sign in</h2> <b-container class="bv-example-row"> <b-row class="justify-content-md-center"> <b-form inline> <b-input id="inline-form-input-name" v-model="username" class="mb-2 mr-sm-2 mb-sm-0" placeholder="Username" ></b-input> <b-input type="password" id="text-password" v-model="password" aria-describedby="Password" placeholder="Password"></b-input> <!-- <button @click="signIn">Signin</button> --> <b-button variant="outline-primary" @click="signIn">Signin</b-button> </b-form> </b-row> <b-row> <b-col><p>You don't have an account? <router-link to="/signup">create account now!!</router-link> </p> </b-col> </b-row> </b-container> </div> </template> <script> import firebase from 'firebase' export default { name: 'Signin', data: function () { return { username: '', password: '' } }, methods: { signIn: function () { firebase.auth().signInWithEmailAndPassword(this.username, this.password).then( user => { // alert('Success!') this.$router.push('/') }, err => { alert(err.message) } ) } } } </script> <style> .signin { line-height:5; } .signin h2{ margin:2rem auto; } button.btn.btn-outline-primary { margin: 0 1rem; } </style>src/components/Signup.vue<template> <div class="signup"> <h2>Sign up</h2> <input type="text" placeholder="Username" v-model="username"> <input type="password" placeholder="Password" v-model="password"> <button @click="signUp">Register</button> <p>Do you have an account? <router-link to="/signin">sign in now!!</router-link> </p> </div> </template> <script> import firebase from 'firebase' export default { name: 'Signup', data () { return { username: '', password: '' } }, methods: { signUp: function () { firebase.auth().createUserWithEmailAndPassword(this.username, this.password) .then(user => { alert('Create account: ', user.email) }) .catch(error => { alert(error.message) }) } } } </script>Computer Vision APIをつかって顔認識のぺーじをつくる
こちらを参考に顔認識のページをつくる
Azure画像認識系の機能をいくつか試してみた(Face API / Custom Vision API / Computer Vision API) - Qiitasrc/components/HelloWorld.vue<template> <div class="hello"> <p class="lh5">{{ msg }}</p> <div id="appFaceAPI-File"> <div class="row"> <div class="col"> <h1>その写真の人が何歳に見えるかおしえてあげよう</h1> </div> </div> <div class="row"> <div class="col"> <div class="form-group"> <!-- https://bootstrap-vue.org/docs/components/form-file --> <div> <!-- Styled --> <b-form-file v-model="file" :state="Boolean(file)" placeholder="Choose a file or drop it here..." drop-placeholder="Drop file here..." @change="handlerFileChange" ></b-form-file> <div class="mt-3">Selected file: {{ file ? file.name : '' }}</div> </div> </div> </div> </div> <div class="row"> <div class="col"> <pre><code>{{ response }}</code></pre> </div> </div> </div> <div class="siout"> <b-button variant="secondary" @click="signOut">Sign out</b-button> </div> </div> </template> <script> import firebase from 'firebase' import axios from 'axios' export default { name: 'HelloWorld', data: function () { return { response: '', fill: null, msg: '良い写真ある?', name: firebase.auth().currentUser.email } }, methods: { signOut: function () { firebase.auth().signOut().then(() => { this.$router.push('/signin') }) }, handlerFileChange: async function (e) { console.log('handlerFileChange') const files = e.target.files || e.dataTransfer.files const file = files[0] let contentBuffer = await this.readFileAsync(file) // console.log(contentBuffer); this.sendCognitiveAsFile(contentBuffer) }, readFileAsync: function (file) { return new Promise((resolve, reject) => { let reader = new FileReader() reader.onload = () => { resolve(reader.result) } reader.onerror = reject reader.readAsArrayBuffer(file) }) }, sendCognitiveAsFile: async function (contentBuffer) { // const FACE_API_ENDPOINT_URL = '***' const qs = require('querystring') const params = qs.stringify({ 'returnFaceId': 'true', 'returnFaceLandmarks': 'false', 'returnFaceAttributes': 'age,gender,headPose,smile,facialHair,glasses,' + 'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise' }) const FACE_API_ENDPOINT_URL = `{取得したエンドポイント}/face/v1.0/detect?${params}` const config = { url: FACE_API_ENDPOINT_URL, method: 'post', headers: { 'Content-type': 'application/octet-stream', 'Ocp-Apim-Subscription-Key': '***' }, data: contentBuffer } // axios try { // POSTリクエストで送る const responseAzure = await axios.request(config) console.log('post OK') // データ送信が成功するとレスポンスが来る console.log(responseAzure.data) // ageを取得 const lookage = responseAzure.data[0].faceAttributes.age this.response = `${lookage} 歳にみえるね` } catch (error) { console.error(error) } } } } </script> <style > .col h1{ font-size:100%; } div.form-group { max-width: 380px; margin: auto; width: 80%; } .lh5 { line-height: 5; } .siout { text-align: right; padding: 1rem; position: fixed; top: 1vh; right: 3rem; } #app{ position:relative; } </style>freenomでドメインを取る
こちらを参考にまずは好きなドメインを取得
爆速!Vercelとfreenomで独自ドメインのサイトを無料で作成する - QiitaNetlifyにDeploy
GitHubのリポジトリを作成して、作成したプロジェクトをPushしましょう。
Netlifyのページへ行き、[Get started in seconds]ボタンを押して
GitHubと連携しましょう
Netlify の管理画面に入れたら、右上にある「New site from Git」を選択します。
GitHubを選びます
先ほど作ったリポジトリを選んでDeploy settingsを埋めていきます
Deploy siteボタンを押せば完了!*ここでエラーになった人!(わたし!)
Gitの作り方によってはPublish先がdistだけでは展開できず、エラーにあることがあります。
Gitの置いてある位置からみているようなので、
works/fbaseApp/distとかになる場合もあります。Deployが進んだら、ご丁寧に次の手順が出ています
2を押しましょう
ここに、freenomで作ったドメインを入れます
とりあえずエラーっぽい感じですが気にせずここを押すと
Alternative: point A record to 1**.**.*.**こういったものが書かれているのでこの番号をだしたまま
つぎにfreenomの管理画面からManage Freenom DNSにいきます
ここに先ほどのNetlifyから取得した番号を入れる
これでしばらく待てばfreenomでとったドメインにへんこうされる!
https://what-age.tk/
ユーザー登録するとどなたでもご覧いただけます。きになるところ
こんなに表示は遅いものなのだろうか・・・
リダイレクトがうまくいっていないような・・・