20200912のNode.jsに関する記事は5件です。

NestJS+TypeORMでDB接続して動作確認まで

基本的に自分用メモです.

DBの準備

今回はmysqlを使います.

予め適当なデータベースとユーザを作成して権限付与までしておきます.テーブルはまだ作らなくてOKです.

CREATE DATABASE testdb;
CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL ON testdb.* TO 'testuser'@'localhost';

NestJSのプロジェクト作成

NestJSのプロジェクトを作成して簡単な動作確認まで行います.

細かいところは公式ドキュメントを参照: https://docs.nestjs.com

npm i -g @nestjs/cli
nest new nest-typeorm-handson // とりあえずnpm選択して進めます
nest g module user
nest g controller user
nest g service user

これでnest-typeorm-handson以下にこんな感じにファイルが生成されます.

src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── main.ts
└── user
    ├── user.controller.spec.ts
    ├── user.controller.ts
    └── user.module.ts

最低限の動作の追加

UserServiceに以下を追加します

user.service.ts
sample(): string {
  return 'UserService';
}

UserControllerに以下のコンストラクタとメソッドを追加します.

user.controller.ts
constructor(private readonly service: UserService) {}
@Get()
get() {
  return this.service.sample();
}

npm run startで起動し,ブラウザでlocalhost:3000/userにアクセスするとUserServiceの文字が出るはずです.

これで準備完了です.

TypeORMを使ってDB接続する

ここからTypeORMの出番です.

まずは準備します.

npm install --save @nestjs/typeorm typeorm mysql

そしてAppModuleの@Moduleの中身のimportsを以下のように書き換えます

app.module.ts
imports: [
  UserModule, // UserModuleはもともと追加されています
  TypeOrmModule.forRoot({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'testuser',
    password: 'password',
    database: 'testdb',
    entities: [],
    synchronize: false,
  })
],

さて,ここでnpm run startします.

すると以下のようなエラーが出る場合があります

[Nest] 70420   - 2020-09-11 22:23:00   [TypeOrmModule] Unable to connect to the database. Retrying (1)... +8ms
Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
    at Handshake.Sequence._packetToError (/Users/home/nest-typeorm-handson/node_modules/mysql/lib/protocol/sequences/Sequence.js:47:14)

これはユーザの設定関連が原因なのでmysqlで以下を実行すると治ります.

ALTER USER 'testuser'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

次にEntityを作ります.src/user/user.entity.tsを作成し,以下のように作ります.

user.entity.ts
import { Entity, PrimaryColumn, Column } from "typeorm";

@Entity()
export class User {
    @PrimaryColumn({ length: 255 })
    id: string;

    @Column({ length: 255 })
    name: string;

    @Column()
    age: number;
}

さらにAppModuleに先ほど書いたDBの接続情報にこのEntityを使うことを教えてあげます.

app.module.ts
TypeOrmModule.forRoot({
            // 一部省略
      database: 'testdb',
      entities: [User], // <-追加
})

UserModuleには以下を追加します.

user.module.ts
imports: [TypeOrmModule.forFeature([User])],

そして,Repositoryの準備です.UserServiceを以下のように書き換えます.コピペでOKです.

user.service.ts
@Injectable()
export class UserService {
    constructor(@InjectRepository(User) private readonly repository: Repository<User>) {}
    sample(): Promise<Array<User>> {
        return this.repository.find();
    }
}

さて,ここでnpm run startしてlocalhost:3000/userにアクセスするとエラーが出ます.

エラー内容はER_NO_SUCH_TABLE: Table 'testdb.user' doesn't existです.そりゃあそうですね,テーブルまだ作って無いですから.

マイグレーション機能でテーブルを作る

EntityなどからDDLを生成することができます.

そのために一旦先ほども書いたDBの接続情報を以下のように./ormconfig.tsにも書きます.

ormconfig.ts
module.exports = {
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'testuser',
    password: 'password',
    database: 'testdb',
    entities: ['src/**/*.entity.ts'],
    migrations: ['db/migrations/*.ts'],
    synchronize: false,
    cli: {
        migrationsDir: 'db/migrations'
    }
}

そしてpackage.jsonのscriptsに以下を追加します

"typeorm": "ts-node ./node_modules/.bin/typeorm --config ormconfig.ts"
npm run typeorm -- -n Initialize

すると./db/migrationsに'数列-Initialize.ts'というファイルが生成され,中にはDDLが書かれていることがわかります.

そして,以下のコマンドを実行するとテーブルが作成されます

npm run typeorm migration:run
mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| migrations       |
| user             |
+------------------+

そして再度npm run startすると起動できます

(もしかするとエラーが出るかもしれませんが,AppModuleのprovidersにUserServiceが指定されていたら消してください.自分の環境ではそれで治りました)

これで接続完了です.テーブルに適当にinsertしてからlocalhost:3000/userにアクセスするとinsertした内容が出力されるはずです.

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

node.jsでサーバーを立ててみる

はじめに

私の最初の投稿になります。
qiitaについても素人同然なので何かあれば教えていただけると幸いです。

これから何回かに分けてチャットウェブアプリを作っていき、その過程をメモとして残していこうと思います。(mac 環境)
今回は node.js を用いてサーバーを立ててみます。

Node.jsのインストール

まずnode.jsがインストールされているか確認をします。
node.jsのバージョンを確認します。
確認は

$ node --version
v12.18.3

で確認。
node.jsが入っていない場合node.jsのインストールをおこなう。
node.jsのバージョン管理はnpm (Node Package Manager) か n を使って管理するとのことです。

今回nを使っていきたいのですが、nを入れるために。(ここら辺もいまいちわかっていないですが)
ともかく以下のコマンドで n をインストールします

$ brew install node
$ npm install -g n

-g オプションはグローバル環境で使えるようにするためにつけるようです。
n の簡単な使い方をまとめてみると

$ n list         #インストール済みのバージョン一覧表示
node/8.2.1
node/12.18.3
$ n latest       #最新版をインストールする
$ n バージョン   #バージョンを指定してインストール
$ n            #何も指定しないとインストールされている中から選択する画面になる

といったところです。

node.jsのパッケージ初期化

まず以下のようにしてnode.jsのパッケージの初期化を行います。

$ npm init

これを行うと

package name: (training) 
version: (1.0.0)
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 

のよう入力を促してくるのでそれぞれ入力してエンターを押して進めていきます。
何も書かずにエンターすると()のなかのものがデフォルトで入力されます。
今回はentry pointのjsファイルの名前をserver.jsにして進めています。

最後yで確定するとpackage.jsonが作られます

pacage.json
{
  "name": "training",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

node.jsのパッケージのインストール

今回使っていくパッケージをインストールしていく
今回はExpressとsocket.ioを使用していくためこの二つを以下のようにしてインストールする

$ npm install express --save
$ npm install socket.io --save

--saveはpackage.jsonのdependenciesに書き込むオプションであるのでpackage.jsonは
余談ーーー
ちなみに--save--devとするとdevDependenciesに書き込まれるらしいです。
この違いはgit clone した時などに必要な環境を整える時にインストールするかどうかを分けているらしいです。
ーーーーー

package.json
{
  "name": "mychat",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "socket.io": "^2.3.0"
  }
}

となる。
またこれによってnode_modulesディレクトリとpackage-lock.jsonファイルが作られる。

サーバーの起動

早速起動していきます。
サーバーの起動を行うファイルserver.jsを作ります(npm initでentry pointで指定した名前で作る)

'use strict';

// モジュール
const express = require( 'express' );
const http = require( 'http' );
const socketIO = require( 'socket.io' );

// オブジェクト
const app = express();
const server = http.Server( app );
const io = socketIO( server );

// 定数
const PORT = process.env.PORT || 1337;

// サーバーの起動
server.listen(
    PORT,
    () =>
    {
        console.log( 'Server on port %d', PORT );
    } );

これについて簡単に説明していきます。
まず

server.js
'use strict';

これは厳格(strict)モードとして実行することの宣言のようです。
つまりエラーなどの許容を厳格にして曖昧な表現を禁止したり動作の高速化、潜在的な問題をわかりやすくするなどあるようです。
次に

server.js
const express = require( 'express' );
const http = require( 'http' );
const socketIO = require( 'socket.io' );

で使われているrequier()
これはモジュールを読み込むために使用するようです。
余談ーーー
htmlでモジュールを読み込む時は<script>タグで行うが、それができないサーバーなどでモジュールを読み込むために作られた仕様のようです。
ーーーーー

server.js
const app = express();
const server = http.Server( app );
const io = socketIO( server );

1行目のexpress() はexpressモジュールの変数の初期化。
2行目の3行目はhttp.Server(app)はhttp.ServerインスタンスにexpressとsocketIOを両立させるために行なっているようです。(この辺りの根本はまだ理解に時間がかかりそうです)

server.js
const PORT = process.env.PORT || 1337;

これはポート番号の指定している。
私がjs歴がほとんどないのでおそらくですがprocess.env.PORTは環境変数PORTを示していて、これをor演算を行なっていると考えると、もし環境変数PORTがfalseを示すものだった場合(None等)数値1337を変数PORTに代入しているのだと思います。
つまり環境変数として指定すればいつでもどのファイルとの統一を測れるのではないかと考えています。

server.js
server.listen(
    PORT,
    () =>
    {
        console.log( 'Server on port %d', PORT );
    } );

server.listen(PORT、function)はサーバーを立ち上げる関数のようです(これは正しいかちょっと自信ないです。)
PORTはポート番号の指定をして、functionは立ち上げた時に働く関数を定義するようです。
今回関数はアロー関数式で定義しています。
(valiable)=>{function}
で記述できるようです。(アロー演算子はあまり使ったことがない)
ここの関数ではconsole.log()でコンソールに標準出力しているだけです。

余談ーーー
サーバーの起動はapp.listen(port)などいろんな方法があるようです。今回はexpressとsocket.ioを同じポートで待ち受けるためにこのような方法をとります。
ーーーーー

これでserver.jsの説明は以上です。
実際にサーバーを起動してみます。起動の仕方は

$node server.js
Server on port 1337

で行う。
止める時はCtrl+Cで止める。

公開フォルダ・ファイルの作成と指定

公開フォルダは実際に公開する公開ファイルを置いておくディレクトリのようです。

$ mkdir public 

これは説明しなくてもいいかと思いますが、publicディレクトリを作っています。
そしてpublicディレクトリの中に実際に表示するindex.htmlファイルを以下のように記述します。

public/index.html
<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>My Chat</title>
</head>
<body>
    <h1>My Chat</h1>
</body>
</html>

htmlについては別にまとめるつもりなのでそちらを参照
続いて公開フォルダを指定します。
server.jsのサーバーの起動の前に

server.js
app.use(express.static(__dirname+'/public'));

express.static関数に公開ディレクトリのパスを渡してあげて指定するようです。ここはまた今度調べます

サーバーにアクセス

実際にサーバーを起動してみてからブラウザの検索画面に"localhost:1337"を入力してアクセスする。
うまくindex.htmlが出てくれば成功です。

クライアントから接続要求と、接続時の処理の作成

この辺りの通信あたりは苦手意識があって忘れてるところも多いので今後勉強していきますので今回は簡単に流します……ごめんなさい
publicディレクトリのなかにクライアントから呼び出すjsファイルclient.jsを作る。

public/client.js
// クライアントからサーバーへの接続要求
const socket = io.connect();

// 接続時の処理
// ・サーバーとクライアントの接続が確立すると、
//  サーバー側で、'connection'イベント
//  クライアント側で、'connect'イベントが発生する
socket.on(
    'connect',
    () =>
    {
        console.log( 'connect' );
    } );

呼び出された時に実行する関数をアロー関数で定義していると思います。
そしてhtmlファイルのbodyの末尾に以下を記述します

public/index.html
<script src="/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="client.js"></script>

そして最後にserver.jsの公開フォルダの指定の上に

server.js
// 接続時の処理
// ・サーバーとクライアントの接続が確立すると、
//  サーバー側で、'connection'イベント
//  クライアント側で、'connect'イベントが発生する
io.on(
    'connection',
    ( socket ) =>
    {
        console.log( 'connection' );
    } );

を記述する。
これでもう一度サーバーを立ててブラウザから"localhost:1337"にアクセスすると、ブラウザの検証のコンソールとサーバーのコンソールにそれぞれ指定した標準出力が表示されれば成功となります。

server.jsを色々書き加えたのでここまでの更新状況を置いておきます。

server.js
'use strict';

// モジュール
const express = require( 'express' );
const http = require( 'http' );
const socketIO = require( 'socket.io' );

// オブジェクト
const app = express();
const server = http.Server( app );
const io = socketIO( server );

// 定数
const PORT = process.env.PORT || 1337;

// 接続時の処理
// ・サーバーとクライアントの接続が確立すると、
//  サーバー側で、'connection'イベント
//  クライアント側で、'connect'イベントが発生する
io.on(
    'connection',
    ( socket ) =>
    {
        console.log( 'connection' );
    } );

// 公開フォルダの指定
app.use( express.static( __dirname + '/public' ) );

// サーバーの起動
server.listen(
    PORT,
    () =>
    {
        console.log( 'Server on port %d', PORT );
    } );

終わりに

今回はチャットウェブアプリを作る前段階としてnode.jsでサーバーを立ててみました。
前にPHPを使ってサーバーを立ててみた時はapacheなど書くことが多く難しい、苦手、といった意識があったのですがnode.jsを使った時は意外と簡単に立てられたのが意外でした。
でもサーバー周りはなかなか理解が追いついてなかったり忘れてたりするのでこれを機に進めながら勉強し直していきたいと考えています。

[参考]

https://www.hiramine.com/programming/chat_nodejs_socketio/01_connect.html [クライアントとサーバーの接続時の処理を作る]
https://techacademy.jp/magazine/16151 [初期化処理を行う!npm initの使い方【初心者向け】]
https://qiita.com/heyheyww/items/092fcbc490a249a2d05c [npm install の --save-dev って何?]
https://qiita.com/havveFn/items/c5beda8572aa8c1e6be6 [npm install時に「--save」オプションはいらない]
https://qiita.com/miri4ech/items/ffcebaf593f5baa1c112 [【JavasScript】use strictとは]
https://qiita.com/uryyyyyyy/items/b10b012703b5396ded5a [require()とは何か?何が便利なのか]
https://teratail.com/questions/8894 node.jsでrequire('http');する理由
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14142502624 知恵袋
https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback Node.js v14.10.1 Documentation
https://expressjs.com/ja/starter/static-files.html Express での静的ファイルの提供

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

Docker ComposeでNode.jsの環境構築

動機

Create React Appを少し遠回りしてはじめようをローカル環境を汚さずに実践したかっため、タイトルのような環境構築を目指しました。

最終的なディレクトリ構成

最終的なディレクトリ構成
node-docker/
    |--docker-compose.yml
    |--node/
        |--app/

docker-compose.yml

Docker Compose 概要
Compose とは、複数のコンテナを定義し実行する Docker アプリケーションのためのツールです。

まずプロジェクトフォルダnode-dockerを作成し、そこにdockercompose.ymlファイルを作成します。

現在のディレクトリ構成
node-docker/
    |--docker-compose.yml
docker-compose.yml
version: '3'
services:
  node:
    image: node:14.9.0-alpine3.10
    container_name: node
    volumes:
      - ./node/app:/app
    tty: true
    ports:
      - 3000:3000
version

docker-compose.ymlのファイルフォーマットのバージョン

services

各コンテナをサービスとして定義できます。

node

サービス名

image

コンテナを実行時に元となるイメージを指定します。
イメージが存在していなければ、ComposeはDocker Hubからpull(取得)を試みます。
Dockerfileからイメージを作成する場合は、例えばnodeディレクトリの下にDockerfileを作成し、以下のように指定します。
build: ./node

container_name

デフォルトで生成される名前の代わりに、カスタム・コンテナ名を指定します。なお、デフォルトだとnode-docker_node_1という名前になります。

volumes

ローカルのパス(左)とコンテナのパス(右)を共有します。

tty

端末を起動するかどうかを指定しています。(多分)

ports

ポートを公開します。ホストとポートを指定(ホスト:コンテナ)するか、コンテナのポートのみ指定します(ホスト側のポートはランダムに選ばれます)。
exposeはポートを露出し、リンクされたサービス間でのみアクセス可能になります。

コンテナの作成と起動

ここまでで必要なファイルが揃ったので、コンテナの作成と起動を行っていきます。
Dockerfileからイメージを作成する場合は、Docker-compose buildする必要がありますが、今回は既にビルドされたイメージをリモートから取得するので必要ありません。
作業はdocker-compose.ymlが置いてあるディレクトリ上で行います。

terminal
$ docker-compose up -d

dオプションにより、バックグラウンドで起動することができます。

nodeコンテナに入る

以上でnode環境は整いましたが、node環境はコンテナ上にあるため、作業はその上でする必要があります。
そのために、起動中のコンテナ内で指定したコマンドを実行できるexecコマンドを利用して、コンテナ上でシェル、端末を起動します。

terminal
$ docker-compose exec node sh

コンテナ内のディレクトリ構成は以下のようになっていて、docker-compose.ymlで指定したためコンテナでのappディレクトリとローカルのnode/appディレクトリは対応しています。

コンテナ内のディレクトリ構成
/
    |--app
    |--bin
    |--dev
    |--home
    |--lib
    |--media
    |--mnt
    |--opt
    |--proc
    |--root
    |--run
    |--sbin
    |--srv
    |--sys
    |--tmp
    |--usr
    |--var

作業は、ローカルと共有できているappディレクトリでした方が良いと思います。

参考

Compose file version 3 reference
Compose ファイル・リファレンス
Docker Compose - docker-compose.yml リファレンス
【初心者向け】Dockerで手軽にNode.js開発環境構築 (2)

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

Node.jsのバージョンあげたら「Node Sass could not find a binding for your current environment」とでた場合

概要

Node.jsのメジャーバージョンをあげると、node-sassがエラーをはくことがある

「Node Sass could not find a binding for your current environment」

そのときは、node-sassをリビルドして自分のnode環境に合った状態にする

対処法

npm rebuild node-sass

これでOK

そのほか対処法

それでもだめなら

npm rebuild node-sass --force

それでもだめなら、いったんnode-sass消してから。いれなおす。
(入れ直しでも、環境にあった適切なbinaryが生成される)

npm uninstall node-saas
npm install node-sass --save-dev
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】API利用の基本テクニック(ローディングの描画と標準時間の変換について)

背景とこの記事の目的

この度、TwitterAPIを利用したWEBアプリを構築しました。
その際に、「あーこの技術は今後も間違いなく活用するだろうなー」と思ったものがいくつかあったので、備忘として残します。
便利なライブラリもあったのでそちら紹介も兼ねます。
※フロントはvueで作っていますので、その前提で以下のトピックを紹介していきます。

目次

1.非同期通信の際のローディングでぐるぐるさせるやつ(スピナー、インジケーター)
2.APIのレスポンスの中にある時刻を日本時間に変換する

非同期通信の際のローディングでぐるぐるさせるやつ(スピナー、インジケーター)

image.png
こういうの実装したいと思ったことは皆さん誰しも一度はあるでしょう。
API通信で時間のかかる処理などでは非常に重宝します。

もし皆さんがvueを使っているのであれば、「vue-loading-overlay」をおすすめします。
詳しい使い方は以下が参考になります。
https://www.npmjs.com/package/vue-loading-overlay
https://www.kabanoki.net/4916/

カスタマイズもある程度できますし、比較的コードの記述量も少ないです。
axiosと組み合わせると以下のような書き方になります。

App.vue
//scriptタグ内の記述
import Loading from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/vue-loading.css';

export default {
  name: 'app',
  data(){
    return {
      isLoading: false,
      fullPage: true,
    }
  },
  components:{
    "loading":Loading
  },
methods:{
      getTimeline:function(){
        alert('Timelineを取得します');

        //overlayの処理
        let self = this;
        self.isLoading = true;

        axios
          .get('APIのURL',
            {
              headers: {
                //必要に応じて記載
              },
              params:{
               //必要に応じて記載
              }
            })
          .then(response => {
            // handle success
            self.isLoading = false;
          })
          .catch(function (error) {
            // handle error
            self.isLoading = false;
            console.log(error);
          })
    },
    onCancel:function() {
      console.log('User cancelled the loader.')
    }
//以下略
App.vue
<!-- templete内の記述 -->
<loading :active.sync="isLoading" :can-cancel="true" :on-cancel="onCancel" :is-full-page="fullPage"></loading>
<button v-on:click="getTimeline" class= "btn btn-primary"> Timelineを取得 </button>

APIのレスポンスの中にある時刻を日本時間に変換する

TwitterのAPIのレスポンスの中に
"created_at": "Thu Apr 06 15:28:43 +0000 2017"
こういうものがありました。
APIのレスポンスには、必ずと言っていいほど時間に関する情報が入っています。そして日本のサービス出ない限り、大抵は標準時で入っています。
これを日本時間に変換する処理をする際は「Moment.js」というライブラリが便利です。

詳しい使い方は下記が参考になります。
https://qiita.com/osakanafish/items/5ef636bbcb2c3ef94953
https://momentjs.com/

実際の例としては以下の通り

App.vue
const moment = require('moment');
require('moment-timezone');

//略(APIの処理)
.then(response => {
let obj = JSON.parse(response.data);
moment.tz.setDefault('Asia/Tokyo'); //日本時間に合わせる

for(var t=0; t<obj.length; t++){
   obj[t].created_at = moment(obj[t].created_at,'dd MMM DD HH:mm:ss ZZ YYYY','en').format('YYYY-MM-DD_HH:mm:ss');
}

日付の記法は地域(というか国?)によってほんとに様々あるようで、それに応じてコーディングも微妙に変える必要があるようですね。

まとめ

実際に作ってみるといろいろと細かいテクニックが必要になることがよくわかりました。
ボタンの活性・非活性の制御、モーダルウィンドウ(子画面)の作り方、無限スクロールの実装の仕方などなど
上げればキリがないのですが、有用そうなものは時間があるときに記事にして残そうと思います

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