Kuzunoha-NEのブログ

プログラミングなどの勉強をしてます

【TypeScript】PostgreSQLとExpressをつなぐ

こんばんは、葛の葉です。

今回はPostgreSQLとExpressを繋いでみましょう。

諸々のバージョン

postgres (docker) postgres:11.5-alpine
node (docker) 8.16.1-alpine
pg(npm package) 7.12.1

データベースの情報

今回はPostgresを使用します。Herokuでも使えると思います。

hostname = postgres
user = postgres
password = example
databaseName = demo
tableName = posts

uri = postgres://postgres:example@postgres:5432/demo

テーブルの中身

demo=# select * from posts;

id |       title        |                         text                         |     name      
----+--------------------+------------------------------------------------------+---------------
  1 | タイトルマン       | メロンはとてもおいしいと思うが、唇が張れると思います | 明後日マン
  2 | はいはい           | ところでドラゴンかわいいよね                         | スター
  3 | いやー探しましたよ | これがおれの本気なのさ                               |  明後日マン
  4 | タイトルです       | ここが本文です                                       |  あっピーマン
  5 | ここがタイトル     | ここが本文                                           |  ここが名前

pgをインストール

nodeのモジュールの一つであるpgを使用します。勉強も兼ねてなのであえてORMは使わないようにしてみました。

www.npmjs.com

npm i pg --save

このような感じのクラスを作る

コンストラクタにPostgreSQLのPoolのオブジェクトを渡すようにする。Poolでなくても良いかも。

// Posts.ts

import {Pool} from "pg"

export class Posts{
    private readonly client:Pool;
    public tableName: String;
    constructor(conn:Pool){
        this.client = conn;
        this.tableName = "posts";
    }

    async row(id:string) {
        let message;
        let query = `SELECT * FROM ${this.tableName} WHERE id=${id}`;
        console.log({ query })
        await this.client.query(query)
        .then(function(result){
            console.log({result})
            message = result.rows
        })
        .catch(function(reason){
            console.log({reason});
            message = reason.message;
        });
        return message;
    }
}

このようにインスタンスを作成する。

import {Pool} from "pg"
import {Posts} from "./Models/Posts"

const uri = "postgres://postgres:example@postgres:5432/demo";
const conn = new Pool({connectionString: uri});
const postsClient = new Posts(conn);
postsClient.row("1").then((data)=>{
    console.log(data);
});

/*
[ { id: 1,
    title: 'タイトルマン',
    text: 'メロンはとてもおいしいと思うが、唇が張れると思います',
    name: '明後日マン'} ]
*/

前回のExpressを見る

前回作ったExpressにシングルトンパターンを組み合わせる。

kuzunoha-ne.hateblo.jp

create_posts関数の引数にURIを渡すようにしている。

// singletons.ts // 新規

import { Pool } from "pg";

import { Posts } from "./Models/Posts"

export let postsClient: Posts;

export function create_posts(uri:string){
    const conn = new Pool({connectionString: uri});
    postsClient = new Posts(conn); 
};

create_posts関数には直接URIを記載しているけれどもprocess.envなどを使って環境変数から取得してくるのもありだと思う。

// factory.ts
import * as express from "express";
import * as bodyParser from "body-parser";

import testRouter from "./controllers/posts"
import { create_posts } from "./singletons" // 追加

export function createApp(){
    const app = express();
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json());
    app.use('/test', testRouter);
    create_posts("postgres://postgres:example@postgres:5432/demo"); // 追加
    return app
}

コントローラーにシングルトンを渡すようにする。また、コントローラーがクエリを受け取った際にPostgresに問い合わせをしたいので asyncawaitを使う。ここは前回から結構変更があるので注意。

// ./controllers/test.ts // 結構変更点あり
import * as express from "express";

import {postsClient} from "../singletons";

const testRouter = express.Router();

testRouter.get('/', async function (req, res) { 
    var ret = await postsClient.row(req.query.id); 
    res.json({
        message:ret
    });
})

export default testRouter;

あとはtypescriptをコンパルして出現したapp.jsを実行する。

$ node app.js
Listen to port http://localhost:8888

http://localhost:8888/test/に対してgetメソッドのURLパラメーターidを渡してあげます。すなわちhttp://localhost:8888/test/?id=1と行った感じです。

$ curl http://localhost:8888/test/?id=1
{"message":[{"id":1,"title":"タイトルマン","text":"メロンはとてもおいしいと思うが、唇が張れると思います","name":"明後日マン"}]}```

と表示されているはずです。

セキュリティ的に

たぶんこれSQLインジェクションできるんじゃないかなって思ったりするんですけど、どうでしょう?自分でテストしてみるのもいいと思います。