20200208のNode.jsに関する記事は10件です。

Macでnodeのバージョンを切り替える〜少しめんどい〜

nodeのバージョンを切り替えるための処理です!
homebrewがインストールされている前提で話をすすめます。homebrewがインストールされてない方はインストールして下さい。

nodebrewがインストールされているか確認

$ nodebrew -v
$ node -v

でnodeのバージョンも確認できる

nodebrewをインストール(インストール済みであれば不要)

$ brew install nodebrew
$ nodebrew -v

でバージョン確認

nodebrewでインストール可能なnodeのバージョンを確認

$ nodebrew ls-remote

ローカルの状況確認(nodebrewを通さずにインストールされているnodeがあれば一旦、アンインストールが必要)

$ brew ls

→nodeが入っていれば一度アンイストールしなければいけない

今まで使っていたnodeをアンインストール

$ brew uninstall --force node

→無理なら$ brew uninstall --ignore-dependencies node

pathを設定

$ vi ~/.bash_profile

vimでファイルを開き、以下を追加する。(iでインサートモード)

export PATH=$HOME/.nodebrew/current/bin:$PATH
bash_profile

→esc、:wqで保存する

$ source ~/.bash_profile

→更新して設定を反映させる。

セットアップ

$ nodebrew setup

→セットアップのためのコマンドで、これやらないとインストールできない。

インストール可能なバージョンを確認

$ nodebrew ls-remote

nodebrewを通して、使いたいバージョンを指定してnodeをインストール

$ nodebrew install-binary 8.11.1

使用するnodeのバージョンを選択

$ nodebrew use v10.15.0

nodeが正しく設定されているか確認

$ nodebrew ls
v8.11.1
v12.14.0

current: v12.14.0

こんな感じになれば正解。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【解決】nuxtでyarnをすると「gyp: No Xcode or CLT version detected!」エラーが発生

環境

  • macOS Catalina 10.15.3 (zsh)
  • node.js v10.15.2
  • yarn 1.21.1
  • vue/cli 4.1.1

事象

nuxt + typescript + vuetify環境を作成するために、以下のテンプレートを使用しました。
https://github.com/nuxt-community/typescript-template

そこでyarnコマンドを実行すると以下のエラーが発生し正常に起動できませんでした。

gyp: No Xcode or CLT version detected!
gyp ERR! configure error 
gyp ERR! stack Error: `gyp` failed with exit code: 1
gyp ERR! stack     at ChildProcess.onCpExit (/Users/dmorita/.nodebrew/node/v10.15.2/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:345:16)
gyp ERR! stack     at ChildProcess.emit (events.js:189:13)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:248:12)
gyp ERR! System Darwin 19.3.0
gyp ERR! command \"/Users/dmorita/.nodebrew/node/v10.15.2/bin/node\" \"/Users/dmorita/.nodebrew/node/v10.15.2/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js\" \"rebuild\"
gyp ERR! cwd /Users/projects/XXXX/client/node_modules/watchpack/node_modules/fsevents
gyp ERR! node -v v10.15.2
success Saved lockfile.
✨  Done in 156.15s.

yarnとしては、Xcodeが見つからないだけなのでsuccessとなってしまうのですが、yarnやnpm辺りに色々と不具合が起こるので解消したいと思います。
私の場合は以下の2stepで解消しました。

Step1

まずは、エラー内容「No Xcode or CLT version detected!」の通りにXcodeとCLTを確認

% xcode-select --install
xcode-select: error: command line tools are already installed, use "Software Update" to install updates

もちろんインストール済みですと。
私の場合、Catalinaにアップデートしてから多々不具合がありました。
こちらを参考に再インストールしました。
https://github.com/nodejs/node-gyp/blob/master/macOS_Catalina.md#i-did-all-that-and-the-acid-test-still-does-not-pass--

Step2

それから再度上記のテンプレートで yarn コマンドを叩くと以下のエラーが発生しました。

xcrun: error: active developer path (\"/Applications/Xcode.app/Contents/Developer\") does not exist
Use `sudo xcode-select --switch path/to/Xcode.app` to specify the Xcode that you wish to use for command line developer tools, or use `xcode-select --install` to install the standalone command line developer tools.
See `man xcode-select` for more details.
gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 1
gyp ERR! stack     at ChildProcess.onExit (/Users/dmorita/.nodebrew/node/v10.15.2/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:262:23)
gyp ERR! stack     at ChildProcess.emit (events.js:189:13)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:248:12)
gyp ERR! System Darwin 19.3.0
gyp ERR! command \"/Users/dmorita/.nodebrew/node/v10.15.2/bin/node\" \"/Users/dmorita/.nodebrew/node/v10.15.2/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js\" \"rebuild\"
gyp ERR! cwd /Users/projects/XXXX/client/node_modules/watchpack/node_modules/fsevents
gyp ERR! node -v v10.15.2
success Saved lockfile.
✨  Done in 150.93s.

え?パスが存在しないだと?
え?CLTのパスじゃなくてXcodeのパスだと?
となり、確認したところ

% xcode-select -print-path                    
/Applications/Xcode.app/Contents/Developer

CLTが参照しているのがXcode.appのDeveloperでした。
この原因の推測としては、過去に Xcode.app→CLT の順にインストールを行ったためXcode.appが先に決定されていたのだと思います。

こちらを参考に参照先のパスをCLTに変更しました。
https://stackoverflow.com/questions/17980759/xcode-select-active-developer-directory-error

% sudo xcode-select -switch /Library/Developer/CommandLineTools

以上

この状態で再度 yarn コマンドを叩くと

% yarn
yarn install v1.21.1
info No lockfile found.
[1/4] ?  Resolving packages...
warning nuxt > @nuxt/webpack > @nuxt/babel-preset-app > core-js@2.6.11: core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
warning " > vuex-class@0.3.2" has unmet peer dependency "vue@^2.5.0".
warning " > vuex-class@0.3.2" has unmet peer dependency "vuex@^3.0.0".
warning " > vuex-class@0.3.2" has unmet peer dependency "vue-class-component@^6.0.0 || ^7.0.0".
[4/4] ?  Building fresh packages...
success Saved lockfile.
✨  Done in 150.13s.

と正常に完了しました。
CatalinaになりNode.js周りで苦しんでる方の悩みが解消できたら幸いです?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】IAM-APIGetway-Lambda-DynamoDB-S3でシンプルなアプリケーション制作

はじめに 

サーバーレス勉強する際に、いろいろ試したメモ、
つまずいた点や、解決法など、主に自分用のメモになります。
他の方の参考にもなれば嬉しいです。

やること

  • DynamoDBでテーブル作成
  • IAMでDynamoDB用のロール作成
  • Lambda上でDynamoDBに値を挿入
  • Lambda上でDynamoDBから値を取得
  • API Gatewayの設定
  • S3にウェブアプリケーションをアップし、公開する
  • 公開されたアプリケーションでDynamoDBを操作する

DynamoDBでテーブル作成

テーブルの作成 をクリックしてください。
UNADJUSTEDNONRAW_thumb_42.jpg
テーブル名プライマリキーを入力します、プライマリキーのタイプは数値を選んでください。
2020-02-07 17.31のイメージ.jpg

デフォルト設定の使用のチェックを外します、今回はテストのため、性能は最低限のもので良いです。
2020-02-07 17.56のイメージ.jpg

設定が終わったら作成をクリックします、しばらくしたらテーブルが作成されるはずです。
UNADJUSTEDNONRAW_thumb_45.jpg

IAMでDynamoDB用のロール作成

権限なしではLambdaがDynamoDB操作できません、Lambda作成する前にまずロールを作ります。
AWSサービスLambdaを選び、次のステップを押してください。

2020-02-07 19.05のイメージ.jpg
Dynamoを検索して、AmazonDynamoDBFullAccessAWSLambdaDynamoDBExecutionRoleにチェック入れてください。
2020-02-07 19.09のイメージ.jpg
タグの追加に関しては、わかりやすいkeyとvalueでいいと思います。
2020-02-07 19.16のイメージ.jpg
ロール名も用途が分かりやすいもので良いです、作成完了したら、Lambdaの方に入ります。
2020-02-07 19.19のイメージ.jpg

Lambda上でDynamoDBに値を挿入

関数を作ります。
関数の作成をクリック。
一から作成を選択、関数名は用途が分かりやすいものにします。
言語は今回Node.jsにします。
アクセス権限は先ほど作って置いたロールを使用します。
設定が終わったら、関数を作成します。
2020-02-07 20.07のイメージ.jpg

関数の内容はリクエストからmessageを受け取って、DynamonDBに保存するというシンプルな内容でした。

index.js
"use strict"
console.log("loading function");
var AWS = require("aws-sdk");
AWS.config.region = "ap-northeast-1";

var docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = function(event, context, callback)  {
  var params = {
      Item:{
          data: Date.now(),
          message: event.message
      },
      TableName: "Lambda-DynamoDB-Write-Read",
  };
  docClient.scan(params, function(err, data){
      if(err){
          console.log("Fail to write into DynamoDB");
          callback(err, null);
      }else{
          console.log("Successfully write into AWS DynamoDB");
          callback(null, data);
      }
  });
};

Lambda上でDynamoDBから値を取得

設定は値挿入時と同じです。
今回はデータの読み取りなので、関数名はLambda-DynamoDB-read-testにします。

index.js
"use strict"
console.log("loading function");
const AWS = require("aws-sdk");
AWS.config.region = "ap-northeast-1"
var docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = function(event, context, callback){
    var params = {
        TableName: "Lambda-DynamoDB-Write-Read",
        Limit: 100
    };
    docClient.scan(params, function(err, data){
        if(err){
            console.log("Fail to read from AWS DynamoDB");
            callback(err, null);
        }else{
            console.log("Successfully Read from AWS DynamoDB table");
            Reflect.set(data, "headers", {
                "Access-Control-Allow-Origin" : "*",
                "Access-Control-Allow-Credentials": "true"}
                );
            Reflect.set(data, "responseCode", 200);
            context.succeed(data);
        }
    });
}

API Gatewayの設定

APIを作成します。
REST APIを選択します。
2020-02-07 21.45のイメージ.jpg
API名と説明を入力して、作成します。2020-02-07 22.21のイメージ.jpg
作成完了後、メニューのアクションから、メソッドの作成を選択して、GETPOSTを作ります。
2020-02-07 22.29のイメージ.jpg

GET

Lambda関数にチェックを入れて、
Lambda関数の所に先程作った、DynamoDBから値を取得用のLambda関数名を入れます。
2020-02-07 22.31のイメージ.jpg
Lambda 関数に権限を追加するのダイアログが出てきますが、okを選択します。
CORSを有効化します、アクションからCORS の有効化にしてください。
2020-02-08 11.45のイメージ.jpg
アクションから、APIのデプロイを行ないます。
2020-02-08 11.47のイメージ.jpg
新しいステージを選択して、
ステージ名*ステージの説明デプロイメントの説明を入力します。
2020-02-08 16.28のイメージ.jpg
デプロイ完成したら、スタージでメソッド確認できます。
2020-02-08 16.45のイメージ.jpg

POST

手順はGET時と基本同じです。
先程作った値を挿入用のLambda関数を使用します。
2020-02-08 17.08のイメージ.jpg
作った後、統合リクエストを選択します。
123.jpg

マッピングテンプレートapplication/jsonContent-Typeに追加します。
2020-02-08 17.21のイメージ.jpg
デプロイする際に、先程作ったステージを選択します。
2020-02-08 17.36のイメージ.jpg
完了後のステージはこんな感じになります。
2020-02-08 17.45のイメージ.jpg

S3にウェブアプリケーションをアップし、公開する

使用するウェブアプリのソースは以下になります。
Jqueryのmin.jsを同じディレクトリに置いてあります。
機能はajax使用して、ApiGatewayにGETとPOSTのリクエストを送り、
DynamoDBにデータ挿入、また取得します。

LamdbaAPIGetwanyDynamoDB.html
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title1</title>
    <script type="text/javascript" src="./jquery-2.1.4.min.js"></script>
</head>
<body>
<div id="entries">

</div>
<h1>Write new comment</h1>
<form>
    <label for="message">Message</label>
    <textarea id="message"></textarea>
    <button id="submitbutton">submit</button>
</form>
<script type="text/javascript">
    var API_Gateway_URL = "APIのURL";
    $(document).ready(()=>{
        $.ajax({
            type: "GET",
            url: API_Gateway_URL,
            success: (data)=>{
                $("#entries").html("");
                data.Items.forEach((getcomments)=>{
                    $("#entries").append("<p>" + getcomments.message+ "</p>");
                })
            }
        });
    });
    $(function () {
          $("#submitbutton").click(() => {
              console.log($("#message").val());
        $.ajax({
            url: API_Gateway_URL,
            type: "POST",
            data: JSON.stringify({"message": $("#message").val()}),
            contentType:'application/json',
            success: function (data) {
                location.reload();
            }
        });
        return false;
    })
    })
</script>
</body>
</html>

S3のパケット作ります

パケット名を入力し、リージョンは東京を使用します。
作成でパケットを作ります。
2020-02-08 18.24のイメージ.jpg
フォルダを作ります。
2020-02-08 18.36のイメージ.jpg
ウェブアプリのファイルとjqueryのmin.jsを同じフォルダーにアップロードします。
2020-02-08 18.39のイメージ.jpg
アップロードされたファイル(HtmlをJqueryのmin.js)にチェック入れて、
アクションから公開します。
2020-02-08 18.58のイメージ.jpg

公開されたアプリケーションでDynamoDBを操作する

公開されたHtmlファイルのurlを開いて、実際操作します。
35cca2645658fe236fc58df751594306.gif
以上で終わりです。:relaxed:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでGoogle Slidesの新規スライド作成

Google Slides APIをNode.jsから触ってみてます。

の記事の続きです。

Google Slideのcreateを試す

スライドの新規作成が出来そうな雰囲気です。

presentations.createのドキュメントを覗くとスライド作成できそうな雰囲気がありました。

準備

Node.jsでGoogle Slides APIを触ってみるでAPIへのアクセスなどを出来るようにしましょう。

ソースコード

前回記事のapp.jsの中身を書き換えます。

  • SCOPESを変更
app.js
const SCOPES = ['https://www.googleapis.com/auth/presentations'];

元々は'https://www.googleapis.com/auth/presentations.readonly'と書いてあって読み込みのみ許可になってました。

  • listSlides(auth)の中身を以下に書き換え
app.js
省略

function listSlides(auth) {
    const slides = google.slides({version: 'v1', auth});
    slides.presentations.create({
        presentationId: '',
    }, (err, res) => {
        if (err) return console.log('The API returned an error: ' + err);
        console.log(res.data);
    });
}

tokenを再発行

token.jsonは読み込みのみ許可のトークンで作成されていたので、一度token.jsonのファイルを削除します。

再度node app.jsで実行すると、URLが発行されて、ブラウザでアクセスするとこのようにアクセス許可を求められます。

スクリーンショット 2020-02-08 16.41.22.png

許可をして、発行されるコードをターミナル側に貼り付けて実行するとtoken.jsonが再発行されて実行できます。

新規スライドが出来た!

実行してみます。

$ node app.js
{
  presentationId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  pageSize: {
    width: { magnitude: 9144000, unit: 'EMU' },
    height: { magnitude: 5143500, unit: 'EMU' }
  },
  slides: [
    {
      objectId: 'p',
      pageElements: [Array],
      slideProperties: [Object],
      pageProperties: [Object]
    }
  ],
  title: '無題のプレゼンテーション',
  masters: [
    {
      objectId: 'simple-light-2',
      pageType: 'MASTER',
      pageElements: [Array],
      pageProperties: [Object],
      masterProperties: [Object]
    }
  ],

省略

Google Drive上で確認してみると無題のプレゼンテーションが作成されてました。

スクリーンショット 2020-02-08 16.43.15.png

所感

とりあえず、Node.jsからスライドの新規作成ができました。

タイトル指定などをして作ることも出来そうなので引き続き探っていきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでGoogle Slides内のテキストを取得してみる

Node.jsでGoogle Slides APIを触ってみるの続きです。

準備

前回の記事を参照して、スライド情報にNode.jsからアクセス出来るようにしましょう。

適当なスライドを用意する

こちらを用意してみました。

https://docs.google.com/presentation/d/1ziVnaFocZ_YF_cuXyXF5PUKGoE62eX-XlnOEslPkKUc/edit#slide=id.p

https://docs.google.com/presentation/d/<ここがプレゼンテーションID>/edit#slide=id.pになるのでこのスライドのプレゼンテーションIDは1ziVnaFocZ_YF_cuXyXF5PUKGoE62eX-XlnOEslPkKUcになります。

Node.jsでGoogle Slidesのテキストを抽出

前回のコードからプレゼンテーションIDの箇所とfunction listSlides(auth)の中身を書き換えてます。

app.js
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');

//変更箇所: プレゼンテーションID - 試すときは自分のスライドでもやってみましょう
const presentationId = `1ziVnaFocZ_YF_cuXyXF5PUKGoE62eX-XlnOEslPkKUc`;

// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/presentations.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';

// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
  if (err) return console.log('Error loading client secret file:', err);
  // Authorize a client with credentials, then call the Google Slides API.
  authorize(JSON.parse(content), listSlides);
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  const {client_secret, client_id, redirect_uris} = credentials.installed;
  const oAuth2Client = new google.auth.OAuth2(
      client_id, client_secret, redirect_uris[0]);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, (err, token) => {
    if (err) return getNewToken(oAuth2Client, callback);
    oAuth2Client.setCredentials(JSON.parse(token));
    callback(oAuth2Client);
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getNewToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) => {
    rl.close();
    oAuth2Client.getToken(code, (err, token) => {
      if (err) return console.error('Error retrieving access token', err);
      oAuth2Client.setCredentials(token);
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
        if (err) return console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

/**
 * Prints the number of slides and elements in a sample presentation:
 * https://docs.google.com/presentation/d/1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Vuc/edit
 * @param {google.auth.OAuth2} auth The authenticated Google OAuth client.
 * 
 */
function listSlides(auth) {
  const slides = google.slides({version: 'v1', auth});
  slides.presentations.get({
    presentationId: presentationId,
  }, (err, res) => {
    if (err) return console.log('The API returned an error: ' + err);

    /* 変更箇所: スライド中のテキストを抽出するテスト */
    const firstPage = res.data.slides[0]; //1スライド目
    const firstBox = firstPage.pageElements[0]; //最初の要素
    console.log(firstBox.shape.text.textElements[1].textRun.content); //中身のテキスト確認
  });
}

素でアクセすると

res.data.slides[0].pageElements[0].shape.text.textElements[1].textRun.content

みたいな感じで超深い階層になるみたいです。

  • res.data.slides: スライドXXページごとの配列 今回はタイトルスライドから抜き出すので0指定
  • firstPage.pageElements: 対象ページのオブジェクト(テキストボックスなど)の配列 今回はページの最初の部ジェクトなので0指定

実行

$ node app.js 
Node.jsてすと

ちゃんとテキストがとれましたね。

所感

返ってくるオブジェクトが結構複雑な印象です。

詳細はslides周りのSDKを覗くのが良さそうだけどどんなオブジェクトが返ってくるかはまだ見つけられてないのでconsole.logで探していったほうが早いかも

追記: 今回使ってるpresentations.getのAPIリファレンスはここっぽい

https://developers.google.com/slides/reference/rest/v1/presentations/get

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでGoogle Slides APIを触ってみる

Google SlidesのAPIをNode.jsで触ってみます。

公式チュートリアルになぞりつつ試したメモ
です。

準備

Node.jsのバージョンは13.7.0です。

  • 作業フォルダ作成
$ mkdir slidesapitest
$ cd slidesapitest
  • 利用モジュールをインストール
$ npm init -y
$ npm i googleapis@39
  • app.jsファイルを作成
$ touch app.js

APIをオンにして、 credentials.jsonを作成

公式チュートリアルEnable the Google Slides APIボタンを押して、APIを有効にし、credentials.jsonを作業フォルダのapp.jsと同じ階層に保存します。

ソースコード

app.jsの中身に以下をコピペ。 公式のままです。

app.js
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');

// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/presentations.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';

// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
  if (err) return console.log('Error loading client secret file:', err);
  // Authorize a client with credentials, then call the Google Slides API.
  authorize(JSON.parse(content), listSlides);
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  const {client_secret, client_id, redirect_uris} = credentials.installed;
  const oAuth2Client = new google.auth.OAuth2(
      client_id, client_secret, redirect_uris[0]);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, (err, token) => {
    if (err) return getNewToken(oAuth2Client, callback);
    oAuth2Client.setCredentials(JSON.parse(token));
    callback(oAuth2Client);
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getNewToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) => {
    rl.close();
    oAuth2Client.getToken(code, (err, token) => {
      if (err) return console.error('Error retrieving access token', err);
      oAuth2Client.setCredentials(token);
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
        if (err) return console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

/**
 * Prints the number of slides and elements in a sample presentation:
 * https://docs.google.com/presentation/d/1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Vuc/edit
 * @param {google.auth.OAuth2} auth The authenticated Google OAuth client.
 */
function listSlides(auth) {
  const slides = google.slides({version: 'v1', auth});
  slides.presentations.get({
    presentationId: '1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Vuc',
  }, (err, res) => {
    if (err) return console.log('The API returned an error: ' + err);
    const length = res.data.slides.length;
    console.log('The presentation contains %s slides:', length);
    res.data.slides.map((slide, i) => {
      console.log(`- Slide #${i + 1} contains ${slide.pageElements.length} elements.`);
    });
  });
}

アクセストークンの発行

app.jsを実行します。

$ node app.js

実行すると、こんな感じのURLが表示されます。

Authorize this app by visiting this url: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fpresentations.readonly&response_type=code&client_id=xxxxxxxxxxxx.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob
Enter the code from that page here: <ここにコードをペースト>

クリックするとブラウザ上で以下のようなコードが表示されるのでコピーします。

Enter the code from that page here: <ここにコードをペースト>の箇所にペーストします。

成功するとtoken.jsonというファイルが保存されて、以下のように元になってるスライドの情報が表示されます。

Token stored to token.json
The presentation contains 5 slides:
- Slide #1 contains 4 elements.
- Slide #2 contains 11 elements.
- Slide #3 contains 9 elements.
- Slide #4 contains 5 elements.
- Slide #5 contains 12 elements.

ソースコードの挙動など

ソースコード上で以下のような箇所がありますが、このプレゼンテーションIDでGoogle Slidesのプレゼンを指定します。

app.js
//省略
 slides.presentations.get({
    presentationId: '1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Vuc',
  }
//省略

https://docs.google.com/presentation/d/1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Vuc/edit#slide=id.ge63a4b4_1_0

先ほど、app.jsを実行した時の表示はスライドの枚数と要素数を表示しています。

The presentation contains 5 slides:
- Slide #1 contains 4 elements.
- Slide #2 contains 11 elements.
- Slide #3 contains 9 elements.
- Slide #4 contains 5 elements.
- Slide #5 contains 12 elements.

所感

取り急ぎ、触ることが出来ました。

思ったより簡単です。

JSONをもっと深ぼっていくとテキスト情報も取れそうですね。

次回やってみます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js - MySQL「Error: Cannot enqueue Query after invoking quit.」の対処(Connection pool)

はじめに

本記事はデータベースにおけるコネクションプール(Connection pool)について触れていくものです。

実際にハマったシチュエーションをもとに説明していきたいと思います。

  • 事象の詳細
  • 原因
  • 状況を再現してみる
  • 適切な対処方法
  • コネクションプールの実装

事象の詳細

Node.jsにて、MySQLからデータを取得しようとしたとき、以下のエラーが発生した。

Error: Cannot enqueue Query after invoking quit.

どうやら2回目のGET:/api/todoを呼び出した時に必ず発生するようです。
その時のソースコードは以下です。

const express = require("express");
const mysql = require("mysql");
const app = express();

// データベースへのコネクションを生成
const connection = mysql.createConnection({
  // DB接続に関するオプション設定
});

connection.connect();

app.get("/api/todo", (req, res) => {
  const query = "SELECT * FROM todo;";

  connection.query(query, (err, rows) => {
    if (err) console.log("err: ", err);

    res.json(rows);
  });

  connection.end();
});

// サーバー起動
app.listen(8081, () => console.log("Example app listening on port 8081!"));

原因

コネクションは再利用できない。

変数connectionに対して、end()を呼び出し、丁寧に接続を切っています。
そこで再度connection()を呼び出せばまた接続できるのでは?という発想でした。

状況を再現してみる

実装した処理

同一のコネクションに対して、複数回接続を行う処理を作成してみました。

const mysql = require("mysql");

const connection = mysql.createConnection({
  // DB接続に関するオプション設定
});

for (let index = 0; index < 2; index++) {
  connection.connect();

  const query = connection.query("SELECT * FROM todo;");
  query.on("result", (row, index) => {
    console.log("--- result ---");
    console.log(row);
    console.log(index);
  });

  connection.end();
}

処理結果

nodeコマンドを使って、この処理を実行すると、以下のようなエラーになります。

Error: Cannot enqueue Handshake after invoking quit.

一度使ったんだから、ちゃんと破棄してくれってことですね。

適切な対処方法

何度もアクセス要求ができる接続窓口を作ってあげる。

実際のWebアプリケーションでは、同一のデータベースに対して何度も接続する処理が行われます。
規模にもよりますが、多人数で利用することを考えると、その数は膨大なものになります。

よって、以下の観点からコネクション確立処理は極力減らしたほうが良いです。

  • 本処理自体がオーバーヘッド(overhead)である
  • コネクション確立には時間がかかるため、ユーザーを都度待たせてしまう
  • コネクションの数だけDB側でメモリを確保する必要があるため、高負荷状態になりやすい

それらを実現するのがコネクションプール(Connection pool)です。

  • コネクションの状態を保持し、そのコネクションを使いまわすことができる
  • コネクション数に上限を設けることができる

コネクションプールの実装

MySQL - Pooling connections を参考に、コネクションプールを作成し、複数回のデータベース接続処理を行ってみます。

使用する関数はcreatePool()です。
実行するクエリが一つの場合、以下のような書き方でOKです。

const mysql = require("mysql");

const pool = mysql.createPool({
  // DB接続に関するオプション設定
});

pool.query("SELECT * FROM todo;", (error, results) => {
  if (error) throw error;
  console.log(results[0]);
});

プールが持つquery関数はpool.getConnection()connection.query()connection.release()を省略してくれます。

複数回のクエリ実行を行いたい場合などは以下です。

pool.getConnection((err, connection) => {
  if (err) throw err;

  connection.query("SELECT something FROM sometable", (error, results) => {
    connection.release();

    if (error) throw error;

    console.log(results[0])
  });
});

おわり

データベースに関する基礎的な知識が不足していたせいで、こんなところでハマってしまいました。
基礎的な部分を固めることができたと思うので、次の課題に取り組んでいきたいと思います。

あと、ライブラリのドキュメントに書いてあるとおりに実装していくと、コールバック地獄に陥りそうですね。
async/awaitなどを使ってもっとスマートに実装していきたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js - Mysql「Error: Cannot enqueue Query after invoking quit.」の対処(Connection pool)

はじめに

本記事はデータベースにおけるコネクションプール(connection pool)について触れていくものです。

実際にハマったシチュエーションをもとに説明していきたいと思います。

  • 事象の詳細
  • 原因
  • 状況を再現してみる
  • 適切な対処方法
  • コネクションプールの実装

事象の詳細

Node.jsにて、Mysqlからデータを取得しようとしたとき、以下のエラーが発生した。

Error: Cannot enqueue Query after invoking quit.

どうやら2回目のGET:/api/todoを呼び出した時に必ず発生するようです。
その時のソースコードは以下です。

const express = require("express");
const mysql = require("mysql");
const app = express();

// データベースへのコネクションを生成
const connection = mysql.createConnection({
  // DB接続に関するオプション設定
});

connection.connect();

app.get("/api/todo", (req, res) => {
  const query = "SELECT * FROM todo;";

  connection.query(query, (err, rows) => {
    if (err) console.log("err: ", err);

    res.json(rows);
  });

  connection.end();
});

// サーバー起動
app.listen(8081, () => console.log("Example app listening on port 8081!"));

原因

コネクションは再利用できない。

変数connectionに対して、end()を呼び出し、丁寧に接続を切っています。
そこで再度connection()を呼び出せばまた接続できるのでは?という発想でした。

状況を再現してみる

実装した処理

同一のコネクションに対して、複数回接続を行う処理を作成してみました。

const mysql = require("mysql");

const connection = mysql.createConnection({
  // DB接続に関するオプション設定
});

for (let index = 0; index < 2; index++) {
  connection.connect();

  const query = connection.query("SELECT * FROM todo;");
  query.on("result", (row, index) => {
    console.log("--- result ---");
    console.log(row);
    console.log(index);
  });

  connection.end();
}

処理結果

nodeコマンドを使って、この処理を実行すると、以下のようなエラーになります。

Error: Cannot enqueue Handshake after invoking quit.

一度使ったんだから、ちゃんと破棄してくれってことですね。

適切な対処方法

何度もアクセス要求ができる接続窓口を作ってあげる。

実際のWebアプリケーションでは、同一のデータベースに対して何度も接続する処理が行われます。
規模にもよりますが、多人数で利用することを考えると、その数は膨大なものになります。

よって、以下の観点からコネクション確立処理は極力減らしたほうが良いです。

  • 本処理自体がオーバーヘッド(overhead)である
  • コネクション確立には時間がかかるため、ユーザーを都度待たせてしまう
  • コネクションの数だけDB側でメモリを確保する必要があるため、高負荷状態になりやすい

それらを実現するのがコネクションプール(connection pool)です。

  • コネクションの状態を保持し、そのコネクションを使いまわすことができる
  • コネクション数に上限を設けることができる

コネクションプールの実装

mysql - Pooling connections を参考に、コネクションプールを作成し、複数回のデータベース接続処理を行ってみます。

使用する関数はcreatePool()です。
実行するクエリが一つの場合、以下のような書き方でOKです。

const mysql = require("mysql");

const pool = mysql.createPool({
  // DB接続に関するオプション設定
});

pool.query("SELECT * FROM todo;", (error, results) => {
  if (error) throw error;
  console.log(results[0]);
});

プールが持つquery関数はpool.getConnection()connection.query()connection.release()を省略してくれます。

複数回のクエリ実行を行いたい場合などは以下です。

pool.getConnection((err, connection) => {
  if (err) throw err;

  connection.query("SELECT something FROM sometable", (error, results) => {
    connection.release();

    if (error) throw error;

    console.log(results[0])
  });
});

おわり

データベースに関する基礎的な知識が不足していたせいで、こんなところでハマってしまいました。
基礎的な部分を固めることができたと思うので、次の課題に取り組んでいきたいと思います。

あと、mysqlライブラリのドキュメントに書いてあるとおりに実装していくと、コールバック地獄に陥りそうですね。
async/awaitなどを使ってもっとスマートに実装していきたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nodeコマンド(non-blockngとblockingについてメモ)

コマンドライン上で操作

node.jsではブラウザなどを使用せずにコマンドライン上で操作ができる

# コマンドライン上での操作開始を宣言
$ node

# この状態で処理を書くと実行される
> console.log('hello world')  # 出力結果: hello world

# 終了する
> .exit

jsファイルから実行

jsファイルsample.jsを作成し、処理を実行させる

# コマンドライン上でjsファイルの処理開始を宣言
$ node sample.js

non-blockngとblockingな書き方

  • nodeはメインのスレッドが1つであるため、処理がブロックされるような書き方はnodeの処理の速さの特徴を殺してしまうためやめるべきである
  • setTimeoutなどのタイマー処理やデータベースへのアクセス、ファイルの書き込みといった命令は処理に時間を要するため、次の命令をブロックしないように書く必要がある

そこで、non-blockingな書き方を採用し、時間がかかりそうな関数はcallback関数で実装することが望ましい

non-blockingな書き方

次の処理をブロックしない書き方のこと

setTimeout(function() {
  console.log('hello');
}, 1000);
conosole.log('world')

setTimeout内の関数をcallback関数という
setTimeoutの内の処理を待たずに次の処理が実行される

blockingな書き方

var start = new Data().getTime();
while (new Data().getTime() < start + 1000);
console.log('world');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gitignoreで指定したディレクトリが読み込まれない

現象

node_modulesディレクトリはリモートリポジトリへpushしなくてよいので、プロジェクトの最上層ディレクトリに.gitignoreを作成し、

.gitignore
node_modules/

を記載。しかし、
git add *するとあらゆるnode_modules/内のファイルがステージされてしまう。

ポイント

.gitignoreは、
「ファイル名」で指定すると配下のフォルダすべてから同名のファイルをすべて該当させるが、
「ディレクトリ名」の場合は現在位置からの相対パスの記載が必要になる。

原因と対処

.gitignore設置ディレクトリからみたnode_modulesディレクトリの位置が

./project/app1/node_modules/

となっていたため、

.gitignore
/project/app1/node_modules/

のように記載。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む