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

Chatwork に Node.js + TypeScript でメッセージを通知する

スクリプト

ちょっとしたツールを作成していて Chatwork 通知が必要になりましたため、作成しました。

import { escape } from 'querystring';
import fetch from 'isomorphic-unfetch'

export async function notify(
  api_token: string,
  room_id: string,
  message: string,
  self_unread?: 0 | 1
): Promise<Response> {
  return await fetch(`https://api.chatwork.com/v2/rooms/${room_id}/messages`, {
    method: 'POST',
    headers: {
      'X-ChatWorkToken': api_token,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: `body=${escape(message)}&self_unread=${
      typeof self_unread === 'undefined' ? 1 : self_unread
    }`,
  });
}

下記のライブラリのインストールが必要です。

# yarn の場合
$ yarn add querystring isomorphic-unfetch

# npm の場合
$ npm install querystring isomorphic-unfetch

querystring.escapeで投稿するメッセージURLパーセントエンコードしています。
これがないと投稿内容にURLで使用できない文字が混ざった際に投稿が失敗します。

また、isomorphic-unfetchはブラウザ(window.fetch)、サーバー(node-fetch)両方で動くfetch APIを提供してくれるため使用しています。

使い方

async function main() {
  await notify(
    process.env.CHATWORK_API_TOKEN,
    process.env.CHATWORK_ROOM_ID,
    "テストメッセージです。"
  );
}
main();

Chatwork API

詳しい仕様は公式ドキュメントをご確認ください。

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

AmazonLinux node.js/typescriptのインストール手順

typescriptを始めたくてAmazonLinuxにnode.jsとtypescriptをインストールしてみた。
まずはcurlでリポジトリを追加してyumでnode.jsをインストールする。

curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
sudo yum install -y nodejs

これでLTSの14.x版がインストールされる。
次にnpmでtypescriptをインストールする。

sudo npm install -g typescript

これでtscが使えるようになり、typescriptのコンパイルができるようになった。

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

ハンズオン Node.jsの7章データストレージ(sqlite3)をTypescriptで試したメモ

概要

前回の続き。
ハンズオン Node.jsの7章データストレージをtypescriptで試す。今回はsqlite。

ソース

環境

package.json
{
  "name": "node-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node ./bin/www",
    "file-system": "node ./bin/www",
    "sqlite": "node ./bin/www",
    "tsc": "tsc",
    "watch": "tsc --watch"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "engines": {
    "node": "14.x"
  },
  "dependencies": {
    "express": "^4.17.1",
    "sqlite3": "^5.0.0",
    "uuid": "^8.3.1"
  },
  "devDependencies": {
    "@types/express": "^4.17.9",
    "@types/sqlite3": "^3.1.6",
    "@types/uuid": "^8.3.0",
    "@typescript-eslint/eslint-plugin": "^4.8.2",
    "@typescript-eslint/parser": "^4.8.2",
    "eslint": "^7.14.0",
    "eslint-config-prettier": "^6.15.0",
    "eslint-plugin-prettier": "^3.1.4",
    "isomorphic-fetch": "^3.0.0",
    "prettier": "^2.2.0",
    "typescript": "^4.1.2"
  }
}

ソース

  • promisifyしたときの型をtype PromiseDbGet = <T>(arg: string, arg2?: any) => Promise<T>としているが、もうちょっとよい方法ないだろうか
src/fqlite/index.ts
import { promisify } from 'util'
import { join } from 'path'
import type { ID, Todo, DataStorage } from '../types'
import type { RunResult, sqlite3 as Sqlite3 } from 'sqlite3'

interface TodoSQLite {
  id: ID
  title: string
  completed: 0 | 1
}
type SQLiteArgs = [sql: string, ...params: any[]]
type PromiseDbGet = <T>(arg: string, arg2?: any) => Promise<T>
type PromiseDbAll = <T>(arg: string, arg2?: any) => Promise<T>

const sqlite3: Sqlite3 =
  process.env.NODE_ENV === 'production'
    ? require('sqlite3')
    : require('sqlite3').verbose()
const db = new sqlite3.Database(join(__dirname, 'sqlite'))

const dbGet: PromiseDbGet = promisify(db.get.bind(db))

const dbRun = function (...args: SQLiteArgs) {
  return new Promise<RunResult>((resolve, reject) =>
    db.run.apply(db, [
      ...args,
      function (this: RunResult, err: any) {
        err ? reject(err) : resolve(this)
      },
    ]),
  )
}

const dbAll: PromiseDbAll = promisify(db.all.bind(db))

dbRun(`CREATE TABLE IF NOT EXISTS todo (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL,
  completed BOOLEAN NOT NULL
)`).catch((err) => {
  console.log(err)
  process.exit(1)
})

function rowToTodo(row: TodoSQLite): Todo {
  return { ...row, completed: !!row.completed }
}

const exportsObj: DataStorage<Todo> = {
  fetchAll: () =>
    dbAll<TodoSQLite[]>('SELECT * FROM todo').then((rows) =>
      rows.map(rowToTodo),
    ),
  fetchByCompleted: (completed) =>
    dbAll<TodoSQLite[]>(
      'SELECT * FROM todo WHERE completed = ?',
      completed,
    ).then((rows: TodoSQLite[]) => rows.map(rowToTodo)),
  create: async (todo) => {
    await dbRun(
      'INSERT INTO todo VALUES (?,?,?)',
      todo.id,
      todo.title,
      todo.completed,
    )
  },
  update: (id, update) => {
    const setColumns = []
    const values = []
    for (const column of ['title', 'completed'] as const) {
      if (column in update) {
        setColumns.push(` ${column} = ? `)
        values.push(update[column])
      }
    }
    values.push(id)
    return dbRun(
      `UPDATE todo SET ${setColumns.join()} WHERE id = ?`,
      values,
    ).then(({ changes }) =>
      changes === 1
        ? dbGet<TodoSQLite>('SELECT * FROM todo WHERE id = ?', id).then(
            rowToTodo,
          )
        : null,
    )
  },
  remove: (id) =>
    dbRun('DELETE FROM todo WHERE id = ?', id).then(({ changes }) =>
      changes === 1 ? id : null,
    ),
}

export default exportsObj

参考

TypeScriptの型入門
TypeScriptでthisの型を指定する
TypeScriptの型システム

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

フロントエンドやるなら入れておくべきESlintってなに?

プログラミング勉強日記

2020年11月28日
昨日の記事でPrettierについて扱ったが、Prettierと合わせて使用することのできるESlintについて紹介する。

ESlintとは

 ESlint(読み方:「イーエスリント」)は、JavaScriptやTypeScriptなどの静的解析ツールである。ESlintを導入することで、単純な構文エラーやプロジェクト固有のコーディング規約を定義することができる。厳密なルールを定義することで、複数人で開発する場合でもシステム全体のコードの一貫性を維持することができる。

ESlintの特徴

  • 自由に多くのルールを設定できる
  • 独自ルールを作成するAPI
  • 固有のライブラリー、フレームワーク、および実践のルールを持つ多数のプラグイン
  • ES6、ES7、JSXの内蔵サポート
  • 迅速に開始できるように、推奨ルールだけでなくサードパーティの設定利用が可能
  • Sublime、Vim、JetBrainsの製品およびVisual Studio Codeなどの、複数のエディタやIDEとの統合が可能

(参考文献:JSプログラマーのイラッとする「クセ」はESLintを導入して対処しよう)

ESlintの導入方法

1. ESlintを使ってTypeScriptを解析するためのライブラリをインストールする

 nodeのルートディレクトリに移動して、eslint, @typescript-eslint/parser, @typescript-eslint/eslint-plugin, eslint-plugin-reactの4つをインストールする。

コマンドプロンプト
npm install --save eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react

2. ファイルを作成して設定する

 eslintrc.jsonファイルを作成して、以下のように設定する。

.eslintrc.json
{
  "parser": "@typescript-eslint/parser", 
  "extends": [
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true // Allows for the parsing of JSX
    }
  },
  "rules": {
    "react/prop-types": "off",
    "@typescript-eslint/no-empty-interface": 0
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

3. package.jsonにscriptを追加する

 package.jsonにESLintが実行できるようにscriptsタグに以下のコマンドを追加する。今回は拡張子が.ts.tsxのみを対象に解析する。

package.json
"lint": "eslint . --ext .ts,.tsx"

4. ESlintの実行

ターミナル
npm run lint

PrettierとESlintが連携してフォーマットを適用させる場合

 Prettierはリントツールと組み合わせて利用することができるので、すでにESlintを使用してるプロジェクトでも使用することができる。
 PrettierとESlintと組み合わせて使用するためには、prettier-eslintとprettier-eslint-cliが必要になる。

 ※Prettierの導入方法はこちらの記事で扱っている。

1. インストールする

 Google JavaScript Style Guideに従ってeslint-config-googleをインストールする。

コマンドプロンプト
npm install -D prettier-eslint prettier-eslint-cli eslint-config-google

2. ESlintの設定をする

 プロジェクトフォルダのルートに、.eslintrc.jsonというファイルを作成して以下の内容を記述する。(Google JavaScript Style Guideに準拠したECMAScript 2018の仕様でリントチェックを行うように定義)

..eslintrc.json
{
  "extends": ["google"],
  "parserOptions": {
    "ecmaVersion": 2018
  }
}

 次に、pakage.jsonに定義したscriptをprettier-eslintを使用したコマンドに変更する。

package.json
{
  "scripts": {
    "format": "prettier-eslint --write 'src/**/*.js'"
  },
  "devDependencies": {
    "eslint-config-google": "^0.13.0",
    "prettier-eslint": "^8.8.2",
    "prettier-eslint-cli": "^4.7.1"
  }
}

3. 実行する

 コマンドプロンプトから以下のコマンドを実行して、Prettierで直されたコードがESlintに渡されてESlintの整形も適応される。

コマンドプロンプト
npm run format

参考文献

Next.jsを勉強してみる その⑤ 〜ESLintを導入する編〜
Prettierの導入方法
フロントエンド開発で必須のコード整形ツール

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

Twitterのリスト内ツイートを取得する際の`slug`の確認方法

TwitterのリストをAPIで使用する際、例えばninomiytアカウントのsampleリストを取得する際、以前は素直に以下のようなコードを書けば取得できました。

node.jsのtwitterライブラリで書いていますが、他の言語でも概ね似たコードになると思います。

const response = await client.get("lists/statuses", {
    "owner_screen_name": "ninomiyt",
    "slug": "sample",
    "count": 100,
});

スクリーンキャプチャ

しかし、現在は新規作成したリストは、slugが作成した名前そのものではなく、名前-数値という値がセットされるようです。以下のようなコードで、lists/listを使って取得して確かめてください。

const result = await client.get("lists/list", {
    "screen_name": "ninomiyt" // あなたのアカウント名に書き換えてね!
});
console.log(result);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SIerエンジニアからWeb系フロントエンドエンジニアに転身するために今やっていること

こんにちは!SIerでJavaプログラマをしているゆうきデザイン(@yuki_design_gr)と言います。

Qiita初投稿として、自己紹介も兼ねて
"SIerエンジニアからWeb系フロントエンドエンジニアに転身するために今やっていること"
というテーマで書いてみようと思います。

同じような境遇にいる人の道しるべの1つになりますように!

目次

1. 自己紹介
2. なぜWeb系を目指すのか
3. SIerエンジニアからWeb系フロントエンドエンジニアに転身するために今やっていること

1. 自己紹介

東京在住の20代半ば。

学歴

東京外大韓国語専攻卒業

職歴

新卒で大手SIerに入社し、アカウント営業を担当(10ヶ月)
→SE(現職。Java・.NET・Oracleのコーディング実務1年半)
→Web系企業への転職準備中

モットー

技術とデザインのことをポジティブに共有すること

目標

・世の中をポジティブにするWebサービスを作ること
・ビジネスを始めたい・サービスを作りたい友だちをIT・デザイン面でサポートすること

趣味など

韓国語と英語は日常会話レベル
持久系のスポーツが好き(陸上・水泳。社会人になってからもたまにやってる)
実写の動画編集
映画・音楽・コーヒー
ミスチルが生まれた時から好きで、人生ピンチの時に助けてくれる存在(←いま)

さあ、本題に入ります!

2. なぜWeb系を目指すのか

①新しいものを追いかけるのが好きだから

音楽や映画などのエンタメや好きで、
SI業界よりもトレンドの移り変わりが激しいWeb業界が楽しそうに見えるため。

②Webサービスを作りたいという目標があるから

自己紹介でも書いたように
世の中をポジティブにするWebサービスを作る
という目標があり、
SI業界に身を置くよりも目標実現への近道だと思っています。

③システムの裏側の処理よりも見た目に魅かれるから

Javaエンジニアをしていてプログラミングは基本的に全般楽しいですが、
JSPやCSS等のシステムの見た目の開発が楽しく、
また他のメンバーが気にかけないレイアウトのズレなどに何度も気づくことができました。

そのため、まずはWebデザイナーやUI・UXデザイナーに興味を持ち、
デザイナーのための勉強会などに参加してきました。

しかし、自分のプログラマとしての経験を活かす×システムの見た目に寄与できる
というフロントエンドの技術が一番しっくりくるなあと今は思っています。

3. SIerエンジニアからWeb系フロントエンドエンジニアに転身するために今やっていること

①フロントエンド技術に触れること

結局はどの言語がベスト!とかはなさそうなので
今は色々触ってみてます。

フロント: react, vue, rails
バックエンドやインフラ等: node, ruby, docker, laravel
その他: TypeScript, Sass, Bootstrap

色々触れる今の時期が一番楽しいですね。
何かを極めた方が勉強になる気もしますが。

個人的にはnode + react(またはvue・angular) + TypeScriptがアツい気がします!
全部jsで書けるなんて!

②フロントエンド技術を用いたWebアプリを作ってみること

上記のそれぞれの言語を使って
簡単なSNSやTodoリストをチュートリアルを見ながら作成中です。
ネット上に公開するところまでやりたいです。

③SNSやGitHub、Qiita等でのアウトプット

個人的に苦手であまりできていないアウトプット。
でも見てる人との交流が生まれたり、自分の特性や技術力を客観視できる機会と思って、
定期的に取り組んでいきたいです。


この投稿の内容は以上です。

ここまで読んでいただきありがとうございます。
これからも有益な情報をポジティブに発信していきたいです。

ぜひ、Twitter(@yuki_design_gr)のフォローもよろしくお願いします。

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

expressでOpenAPI仕様のAPIを実装するときのTips

要点

  1. express-openapiを使おう (openapi-generatorではなく)
  2. security handlerの実装には、シンプルにOpenAPIのinitializeメソッドに引数securityHandlersを渡すのが良い。(openapi-security-handlerは、必要ない)

express-openapiを使おう (openapi-generatorではなく)

OpenAPI仕様のYAMLファイルを Swagger Editor (https://editor.swagger.io/) などを用いて作った後、openapi-generator (https://github.com/OpenAPITools/openapi-generator) を使ってスケルトンを作れそう...と思うのですが、openapi-generatorの出力するNode.js向けのスケルトンは、あまり良い感じではないです。

むしろexpress-openapi (https://www.npmjs.com/package/express-openapi) の方が、使う機能だけ使って書くように考えられているので、やりたいことに集中できるように思います。

security handlerの実装には、シンプルにOpenAPIのinitializeメソッドに引数securityHandlersを渡すのが良い。(openapi-security-handlerは、必要ない)

APIサーバを実装するとき、ほとんどの場合、セキュリティのことも考えることになると思います。

このとき、express-openapiのHighlightsを見ると、こういうことが記述されています。

Leverages security definitions for security management.
* See openapi-security-handler

openapi-security-handlerを使う必要があるのかな...と感じますが、必要ありません。

express-openapiでセキィリティハンドラを実装するときは、initializeメソッドの引数secuurityHandlersを渡すのが正解です。

initialize({
  apiDoc: apiDoc,
  app: app,
  securityHandlers: {
    keyScheme: function(req, scopes) {
      return Promise.resolve(true);
    }
  }
});

reqはexpressでおなじみのもので、scopesには、以下のようにAPIの定義のsecurityでスキーマ別に定義するスコープ配列が渡ってきます。

security:
- keyScheme: [admin]

セキュリティハンドラの戻り値は、即値でbooleanを返すか、booleanを返すPromiseとします。

trueが認証OKで、falseがNGです。

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

nodeJSとGoogleSpreadSheet(スプレッドシート)を連携する方法

nodeJSとスプレッドシートを連携して、スプレッドシート上の値を取得するところまでを解説します。

順序
①. auth情報を取得する
②. スプレッドシート側で権限を確認する
③. nodeJSのコードを書いて実行する

の3本立てで説明しました。

①. auth情報を取得する

Google Cloud Platform(Google API)でGoogleSpreadSheetのAPIを有効化させます。すでに"サービスアカウント"の認証情報のjsonを持っている人は不要です

「プロジェクトの選択」→「新しいプロジェクト」へ。
image.png

image.png
必須の入力箇所のみ適当に入れてあとは空白(もしくは任意)で進めていきます。
image.png
プロジェクトを立てたら「APIとサービス」へいって下記の流れでGoogle Spread Sheet APIを有効化させます。
image.png

image.png

image.png

image.png

image.png

画面の左端の方の「認証情報」をクリックしてから「認証情報を作成」をクリックします。下記の流れで"サービスアカウント"を作成してキーの入ったjsonファイルをDLします。
image.png

image.png
必須項目より他は空白で大丈夫です(もしくは任意)
image.png
画面左端の「認証情報」のページを改めて開くと"サービスアカウント"の中にアカウントが追加されているのでクリックします。
image.png

ここでjsonファイルとして鍵をDLすればGoogle API側の準備は完了です。
image.png

②. スプレッドシート側で権限とスプレッドシートIDを確認する

次に対象となるスプレッドシート側で権限を確認しましょう。。
image.png

ユーザーやグループと共有の中に自分が権限を与えたいメールアドレスが含まれているかどうかを確認し、なければ追加します。
image.png

ここで、①. で用意した秘密鍵の中身を確認してください。

{
  "type": "service_account",
  "project_id": "test",
  "private_key_id": "hoge",
  "private_key": "hoge,
  "client_email": "ここにスプレッドシート側で権限が付与されているメールアドレスを入力する",
  "client_id": "hoge",
ー以下略ー
}

client_emailの中のメールアドレスはスプレッドシート側で権限を付与されたメールアドレスになります。

次にスプレッドシートIDを確認します。
スプレッドシートのページのURLに含まれており、ランダムな英字と記号となっています。スプレッドシートの部分をコピーしておきます。
https://docs.google.com/spreadsheets/d/スプレッドシートID/edit=hoge

③. nodeJSのコードを書いて実行する

では①. と ②.の過程で準備した情報を使って見ます。ライブラリを用意。

yarn add google-spreadsheet-as-promised
or
npm install --save google-spreadsheet-as-promised

Auth認証→スプレッドシートの1行目のAからDまでを取得するコードです。スプレッドシートに適当な文字を入力して、下記コードを実行してみてください。

spreadsheet.js
const GoogleSpreadsheetAsPromised = require('google-spreadsheet-as-promised');
const CREDS = require('./GoogleCloudからDLしてきた鍵.json');
const SHEET_ID = 'あなたのスプレッドシートID';

const spreadSheet = async() => {
  console.log('GoogleSpreadSheetへアクセス開始します');
  const sheet = new GoogleSpreadsheetAsPromised();;
  await sheet.load(SHEET_ID, CREDS);
  console.log('auth完了');

  const worksheet = await sheet.getWorksheetByName('sheet1');
  const cells = ( await worksheet.getCells('A1:D1') ).getAllValues(); // A1からD1までの値を取得する
  console.log(cells);

  return(sheet);
}

spreadSheet();

  const worksheet = await sheet.getWorksheetByName('sheet1');

のところは任意のシート名にします。読み込めなかった場合はシート名も見直してみてください。

結果として配列の結果が取得できたと思います。

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

ハンズオン Node.jsの7章データストレージ(file-system)をTypescriptで試したメモ

概要

ハンズオン Node.jsの7章データストレージを試す。
型をつけて、typescriptで書いてみる。
今回はfile-systemの章。
環境はnodeのexpressをtsで作って無料でazureに公開したメモで作成したものを利用した

ソース

環境

package.json
{
  "name": "node-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node ./bin/www",
    "file-system": "node ./bin/www",
    "tsc": "tsc",
    "watch": "tsc --watch"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "engines": {
    "node": "14.x"
  },
  "dependencies": {
    "express": "^4.17.1",
    "uuid": "^8.3.1"
  },
  "devDependencies": {
    "@types/express": "^4.17.9",
    "@types/uuid": "^8.3.0",
    "@typescript-eslint/eslint-plugin": "^4.8.2",
    "@typescript-eslint/parser": "^4.8.2",
    "eslint": "^7.14.0",
    "eslint-config-prettier": "^6.15.0",
    "eslint-plugin-prettier": "^3.1.4",
    "isomorphic-fetch": "^3.0.0",
    "prettier": "^2.2.0",
    "typescript": "^4.1.2"
  }
}

ソース

メインのソース

  • ステータスコードは数字でみても分かりにくいので定数にした
app.ts
import express from 'express'
import { v4 as uuidv4 } from 'uuid'
import { statusCode, paths } from './constants'
import type { Todo, DataStorage, HttpError, MiddlewareHandler } from './types'

// const dataStorage: DataStorage<Todo> = require(`./${process.env.npm_lifecycle_event}`)
//   .default
const dataStorage: DataStorage<Todo> = require('./file-system').default

const app = express()
app.use(express.json())

// ToDo一覧の取得
app.get(paths.todos, (req, res, next) => {
  if (!req.query.completed) {
    return dataStorage.fetchAll().then((todos) => res.json(todos), next)
  }
  const completed = req.query.completed === 'true'
  dataStorage.fetchByCompleted(completed).then((todos) => res.json(todos), next)
})

// ToDoの新規登録
app.post(paths.todos, (req, res, next) => {
  const { title } = req.body
  if (typeof title !== 'string' || !title) {
    const err: HttpError = new Error('title is required')
    err.statusCode = statusCode.BadRequest
    return next(err)
  }
  const todo = { id: uuidv4(), title, completed: false }
  dataStorage
    .create(todo)
    .then(() => res.status(statusCode.Created).json(todo), next)
})

// Completedの設定、解除の共通処理
function completedHandler(completed: boolean): MiddlewareHandler {
  return (req, res, next) =>
    dataStorage.update(req.params.id, { completed }).then((todo) => {
      if (todo) {
        return res.json(todo)
      }
      const err: HttpError = new Error('Todo not found')
      err.statusCode = statusCode.NotFound
      next(err)
    }, next)
}

// ToDoのCompoetedの設定、解除
app
  .route(`${paths.todos}/:id/completed`)
  .put(completedHandler(true))
  .delete(completedHandler(false))

// Todoの削除
app.delete(`${paths.todos}/:id`, (req, res, next) =>
  dataStorage.remove(req.params.id).then((id) => {
    if (id !== null) {
      return res.status(statusCode.NoContent).end()
    }
    const err: HttpError = new Error('Todo not found')
    err.statusCode = statusCode.NotFound
    next(err)
  }, next),
)
export default app
file-system/index.ts
import { extname } from 'path'
import { readdir, readFile, writeFile, unlink } from 'fs/promises'
import type { Todo, DataStorage } from '../types'

const exportsObj: DataStorage<Todo> = {
  fetchAll: async () => {
    const files = (await readdir(__dirname)).filter(
      (file) => extname(file) === '.json',
    )
    return Promise.all(
      files.map((file) =>
        readFile(`${__dirname}/${file}`, 'utf8').then(JSON.parse),
      ),
    )
  },
  fetchByCompleted: (completed) =>
    exportsObj
      .fetchAll()
      .then((all) => all.filter((todo) => todo.completed === completed)),
  create: (todo) =>
    writeFile(`${__dirname}/${todo.id}.json`, JSON.stringify(todo)),
  update: async (id, update) => {
    const filename = `${__dirname}/${id}.json`
    return readFile(filename, 'utf8').then((content) => {
      const todo = { ...JSON.parse(content), ...update }
      return writeFile(filename, JSON.stringify(todo)).then(() => todo)
    })
  },
  remove: (id) =>
    unlink(`${__dirname}/${id}.json`).then(
      () => id,
      (err) => (err.code === 'ENOENT' ? null : Promise.reject(err)),
    ),
}
export default exportsObj
constants.ts
import type { ValueOf } from './types'
export const statusCode = {
  Created: 201,
  NoContent: 204,
  BadRequest: 400,
  NotFound: 404,
} as const
export type StatusCode = ValueOf<typeof statusCode>

export const paths = { todos: '/api/todos' }

型定義

  • 各ファイルに分かれた定義をまとめてexport
types/index.d.ts
export type * from './storage'
export type * from './todo'
export type * from './utils'
export type * from './http'
  • ハンズオンに書かれていたStorageの仕様を型にした
types/storage.d.ts
import type { ID } from './common'
import type { OptionalKeys } from './utils'
export interface DataStorage<T> {
  fetchAll: () => Promise<T[]>
  fetchByCompleted: (completed: boolean) => Promise<T[]>
  create: (todo: T) => Promise<void>
  update: (id: ID, update: OptionalKeys<T>) => Promise<T | null>
  remove: (id: ID) => Promise<ID | null>
}
  • 型を定義するための汎用的な型をutilsとしてまとめた
types/utils.d.ts
export type ValueOf<T> = T[keyof T]
export type OptionalKeys<T> = { [K in keyof T]?: T[K] | null }
  • IDはエイリアスを切っただけ
types/common.d.ts
export type ID = string
types/todo.d.ts
import type { ID } from './common'
export interface Todo {
  id: ID
  title: string
  completed: boolean
}
  • ErrorにはstatusCodeプロパティがないので自前で定義
  • expressの、引数を3つとるMiddlewareの型がなかったのでRequestParamHandlerを参考に定義
http.d.ts
import type { Request, Response, NextFunction } from 'express'
import type { StatusCode } from '../constants'
export interface HttpError extends Error {
  statusCode?: StatusCode
}
export type MiddlewareHandler = (
  req: Request,
  res: Response,
  next: NextFunction,
) => void

テスト

  • テストはVsCodeのREST Client拡張を使用した

image.png

tools/connectionTest/todos.azure.http
GET https://az-node-app.azurewebsites.net/api/todos HTTP/1.1

###
POST https://az-node-app.azurewebsites.net/api/todos HTTP/1.1
content-type: application/json

{"title": "テスト"}

###
# タイトルがないと400エラー
POST https://az-node-app.azurewebsites.net/api/todos HTTP/1.1
content-type: application/json

{}

###
# 一つ目の要素をcompletedにする
PUT https://az-node-app.azurewebsites.net/api/todos/29010728-d64e-4db2-b49e-d7c2daf09a9a/completed HTTP/1.1

###
# 一つ目の要素のcompletedを解除
DELETE https://az-node-app.azurewebsites.net/api/todos/29010728-d64e-4db2-b49e-d7c2daf09a9a/completed HTTP/1.1

###
# 一つ目の要素を削除
DELETE https://az-node-app.azurewebsites.net/api/todos/29010728-d64e-4db2-b49e-d7c2daf09a9a HTTP/1.1

参考

ハンズオン Node.js

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

【Nuxt.js】Vue CLIによりアプリケーション雛形を作るまで

スクリーンショット 2020-11-28 10.00.51.png

言語化するために記事に起こしました。
汎用性の高いスターターテンプレートで雛形を作成するまでを簡潔に記します。

事前準備

①Node.jsの導入
②Yarnの導入
③direnvの導入

①、②共にNuxt.jsで開発する上で必須となるので導入する(ここではこれらの導入方法については省略します)。

③はターミナルのcurrentディレクトリで環境変数を自動で設定してくれるツール。環境変数の設定忘れを防止するため導入するとよい。

1

ターミナル
npm i -g @vue/cli @vue/cli-init

上記コマンドで、Vueコマンドを追加する。「Vue -V」でバージョン確認。

ターミナル
$ vue -V
  @vue/cli 4.5.9

2

1により、「vue init」コマンドでプロジェクト作成可能。
今回は初学者やカスタマイズして利用したい方におすすめのテンプレートである、「my-first-nuxt-app」を利用する。

desktop
$ vue init nuxt-community/starter-template my-first-nuxt-app

インストール中のいくつかの質問は全てEnterでOK。
作成後、

ターミナル
$ cd my-first-nuxt-app #ディレクトリに移動
$ yarn #パッケージをインストール
$ yarn dev # 開発モードでプロジェクトを起動

OPEN http://localhost:3000
と表示後、上記URLにアクセスする。

3

スクリーンショット 2020-11-28 10.00.51.png

このような表示が出れば完了です!

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