2021個人開発Webサービス案

明けましておめでとうございます。2021年もよろしくお願いします。

さて、今年の目標の1つとして、今年こそ個人のWebサービスを開発しようと思っています。
開発しようと思っているサービスと、使う予定の技術はざっと以下のような感じです。

サービス名
 Tramories トラモリーズ(travel + memories

サービス概要
旅の写真(昔の写真も含む)を共有し合うサービス

サービス詳細
投稿機能(写真+簡単な説明)、タグ、コメント、いいね、ユーザーフォロー
 OAuth2認証(検討中)オススメ機能(検討中)

一般的なミニSNSっぽい感じですが、

使用技術
 サーバーサイド
  JavaSpring Boot(機械学習を入れる場合はPythonFlask
Laravelを使うかかなり迷ったけど、今仕事で使っていることだし、Spring Bootにしようかと。
機械学習でおすすめ機能とかスパムフィルターとか取り入れるんだったら、Flaskで機械学習API作るのもありかなと思ったり。

 フロントエンド
  TypeScriptReact
フロントエンドのフレームワークはこれかなと。JavaScriptで行くかTypeScriptにするかは若干迷っているけど。

 データベース
  MySQL
ここはこうなるか。PostgreSQLは一度も触ったことがないんで。最近流行りのNoSQLも今回はいいかなと。

 Webサーバー
  Apache
ここもこれしかないか。Nginxも最近は増えてきているようだけど、Apacheで。

 インフラ
  Microsoft AzureHerokuなども検討中)
ここが一番の悩みどころ。以前AzureにWordpress入れたら1ヶ月で6000円とかしてすごい焦ったもんなあ。
誰かWebサービスを安くデプロイする方法を知っている方教えていただけるとありがたいです。

ドキュメント、その他
 画面仕様書、テーブル定義書、機能設計書(処理フローなど)、単体テスト仕様書、結合テスト仕様書
個人サービス作る場合ってドキュメントとか公開するものなのかなあ?公開しないにしても仕事の練習になるだろうし、作ろうとは思っています。

少しづつ作って行って、何とか年内には公開したいなあ。

TypeScript入門 その3

TypeScript入門3回目の今回は、データ型の中でも、前回紹介できなかった列挙型(enum)、タプルや関数オブジェクトあたりについて説明しようと思います。

1. 列挙型(enum)
列挙型(enum)とは、あらかじめ決められた値のみを取ることができるデータ型です。例えば、こんな感じです。

enum Signal {
 Red, Yellow, Blue }; let red = Signal.Red; let yellow = Signal.Yellow; let blue = Signal.Blue; console.log(red); // 0 console.log(yellow); // 1 console.log(blue); // 2 // let green = Signal.Green // コンパイルエラー!

このようにして定義された変数signalは、Red, Yellow, Blueのいずれかしか代入することが出来なくなっています。
また、enumはデフォルトでは内部では0から始まる整数を取るようになっていますが、

enum Signal {
  Red = 2,
  Yellow = 3,
  Blue = 4
};

というように、定義するときに取る値を変更することもできます(数値だけでなく、文字列を入れたりもできます)。
ところで、最近ではenumを使わない方がいいという意見もあるようです。
enumを使わない書き方としては、こんな感じです。

const Signal = { 
  Red: 0,
  Yellow: 1,
  Blue: 2
} as const;
type Signal = typeof Signal[keyof typeof Signal];
let red = Signal.Red;
console.log(red);  // 0

このような有限集合のデータ型をUnion型と言います。
TypeScriptの経験が浅い自分はenumのデメリットを把握しきれてないのですが、enumはJavaScriptとの互換性がないというのは確かにありますね(コンパイル後のソースを見ると一目瞭然)。

2. タプル
次は、複数の型の値をまとめて持つことができるタプルというものを紹介します。
配列のように複数のデータ型を宣言することで定義できます。
例えば、こんな感じです。

let person: [string, number, string];
person = ["Taro", 26, "Tokyo"];
console.log(person[0]); // Taro
console.log(person[1]); // 26
console.log(person[2]); // Tokyo
person[1] = 30; // OK
person[1] = "30"; // コンパイルエラー

とはいえ、この書き方だと何を表しているかわからなくなりがちなので、下記のような使い方の方がわかりやすいかもです。後、先ほどのenumももちろん要素にすることができます。

type name = string;
type age = number;
type address = string;
enum gender {male, female};

let person: [name, age, address, gender];
person = ["Taro", 26, "Tokyo", gender.male];

3. 関数オブジェクト

TypeScriptでも、JavaScriptと同じように関数の定義を行えますが、やはりそこはTypeScript。引数と戻り値に型を指定することができます。

function multiple(a:number, b:number):number {
  return a * b;
}
multiple(5, 3); // 15
multiple(5, "3"); // コンパイルエラー

こんな感じです。
また、TypeScriptでは下記のようにオプション引数を使うことができます。

function multiple(a:number, b:number, c?:number):number {
  if (c) {
    return a * b * c;
  }
  return a * b;
}
multiple(5, 3); // 15
multiple(5, 3, 4); // 60

後は、同じ名前の違う型で定義された関数も定義することができます。いわゆるオーバーロードです。

function add(a:string, b:string):string;
function add(a:number, b:number):number;

function add(a:any, b:any):any {
    if (typeof a === "string" && typeof b === "string") {
        return a + ":" + b;
    }
    return a + b;
}
add("Hello", "World"); // Hello:World
add(7, 4); // 11
add("Hello", 4); // コンパイルエラー

 

今日はこの辺りで。
次回最終回(予定)はクラスとかのオブジェクト指向に関する話題を紹介しようと思います。

TypeScript入門 その2

前回のTypeScript入門その1に続いて、第2回の今回は、主にデータ型などを説明しようと思います。

1. 主要なデータ型
基本的なデータ型は、次の3つです。
・boolean
取ることができる値はtrueかfalseのどちらかだけです。
・number 
数値を値に取ることができます。整数、小数の区別はありません。
・string
文字列を値に持つことができます。

変数を宣言する時は、下記のようにして型とともに宣言します。

let enabled:boolean;
let money:number;
let name:string;
enabled = true;
money = 100;
name = "Ichiro";

money = "ABC";   // これはダメ。コンパイル時にエラーになります。

このように型を宣言すると、その変数には型に当てはまる値以外は入れられなくなります。JavaScriptでは10 == “10”がtrueになったりということもありましたが、TypeScriptではそもそも比較自体ができなくなります。
とはいえ、何らかの理由で型を指定したくない場合も場合によってはあるかと思います。このような場合はlet value:anyという風にany型を使うことでどのような型でも代入できるようになります。JavaScriptで書かれたソースをTypeScriptに書き換える場合なんかでたまに使うこともあるかもです。

2. 変数宣言の指定子
先ほどの変数宣言の例ではletを使って宣言しましたが、varだと関数の中での宣言なら関数内で、それ以外ならグローバルで使えるようになりますが、letは宣言された構文内のみがスコープになります。
varよりはletを使った方が想定外のところで値が書き換わるようなことがなくなるので、varにしなければいけない理由がある場合以外はletを使った方がいいと思います。
ちなみに、letはJavaScriptではES6から使えるようになりました。
また、変数宣言ではvarとletですが、定数宣言ではconstを使います。constで宣言されると、それ以降値の変更が一切できなくなります。
最近letも使わずconstだけを使うべきだという記事も見ましたが、確かに値を変更することがありえない場合はconstを使うべきですが、letでないと実装しづらいケースも多々あると思うので、個人的には「そこまでこだわらなくてもいいのでは」派です。

3. 配列
TypeScriptにも、JavaScriptと同じように配列は存在します。配列の宣言は下記のように行います。

let data:number[];
data = [1, 1, 2];
data.push(3);
console.log(data);   // 1,1,2,3
data.push("xyz");    // だからダメだってば

このように、配列もどんな型を要素に入れられるかの宣言を行います。なので、配列の要素に指定した型でないものを入れようとするとエラーになります。

この他にも、列挙型(enum)やタプルについても説明したかったのですが、次回にします。

TypeScript入門その1

  1. AltJSとは
    Webプログラミングのサーバー側言語はPHP、Java、Python、Ruby、・・・とたくさんありますが、フロントエンド側の言語となると、ほぼJavaScript一択。しかしJavaScriptにも下記のような問題点があったりします。
    ・型付けが自由すぎてそれに由来する想定外の問題が起きやすい(例えば1==”1″がtrueになったり)
    ・オブジェクト指向が独特(プロトタイプベース)
    ・スコープを絞りにくいそこで、最近ではこう行った問題を解決するために、より実装しやすいプログラミング言語(AltJSと言います)で実装してJavaScriptに変換するというのが流行りになりつつあります。
    というわけで、これから何回かに渡って代表的な AltJSの1つであるTypeScriptについて紹介していこうと思います。
  2. TypeScriptの特徴
    TypeScriptは2012年にマイクロソフトによって開発された言語で、下記の特徴を持っています。
    ・JavaScriptのスーパーセットである(JavaScriptの機能はTypeScriptでも使える)
    ・静的型付けの言語である(数値型の変数に文字列を代入したりするとエラーになる)
    ・クラスやインターフェースなど一般的なオブジェクト指向の機能を持っている(クラスについてはJavaScriptでもES6から使えるようになりましたが)
  3. TypeScriptの環境構築
    まず前提として、npmが使える環境であるとします。とりあえず、Windowsならコマンドプロンプトで、MacならiTerm2で”npm -v”と打ってみて、バージョンが出てくればOKです。入っていない人はこちらを参考にしてNode.jsとnpmをして下さい。
    次に、TypeScriptをインストールします。
    npm install -g typescript
    インストールし終わったら、とりあえず下記の内容をコピーして、.tsを拡張子につけたファイル名(例えばhello.ts)で保存して下さい。

    class Hello {
      public name: string;
      constructor(name: string) {
        this.name = name;
      }
    }
    
    let user = new Hello("World");
    console.log("Hello " + user.name);

    そしたら、”tsc hello.ts”で保存したファイルをコンパイルすればhello.jsというJavaScriptのファイルができているはずです。
    後は、HTMLに埋め込んでブラウザで開けば、検証ツールで”Hello World”と出力されることが確認できますし、node hello.jsでターミナル上で出力を確認してもいいでしょう。次回はTypeScriptで使えるデータ型などについて説明しようと思います。

Reactでnpm startがうまくいかない時にやったこと

ReactとReduxでWebアプリを作るための前段階としてとりあえずこちらのチュートリアルをやってみようとしたのだが、途中でnpm startを実行したところエラーが出てにっちもさっちもいかない状況になってしまった。

こんな感じ。

package.jsonの中身を確認して、

“scripts”: {
“start”: “react-scripts start”,

が記述されていることも確認したし、ネットで調べて見つけたpackage-lock.jsonとnode_modulesを消してnpm installを再実行する方法を試してもうまくいかず、nodeとnpmを再インストールしてもダメで困り果てていたが、create-react-appを再インストールして見たら、なんとうまく行った!

チュートリアルをやってみて作ったTodoアプリおよびこれからやってみたいこと

Angular入門その1

JavaScriptのフレームワークの1つ、Angular。今の所は勉強中といったところですが、これから自己学習を兼ねて何回かに分けてAngularについて投稿していこうと思います。
使い方的にはVue.jsと似ているところもあるようですが、AngularはTypeScript(JavaScriptをわかりやすく改良したもので、これをコンパイルするとJavaScriptになる)を使うことが推奨されているので、それについても別途記事を書こうかと。ちなみに、もともとAngularJSと呼ばれていたものが、JavaScriptでなくTypeScriptが推奨されるようになってからJSが外れてAngularと呼ばれるようになったようです(なので、上でJavaScriptのフレームワークと書いていますが、フロントエンドのフレームワークといった方が正確なのかもしれないです)。
なお、Vue.jsの入門記事は その1 その2 その3 (近いうちにこちらに移転します)

1. 環境構築
Angularは、一般的には「Angular CLI」というコマンドラインツールを使って環境構築することが多いようです。流れとしては、ざっとこんな感じです。
i) Node.jsをインストール
とりあえず、Macなら

$ brew install nodebrew

でOK。Homebrewが入ってなければそれを入れるのが先だけど。
WindowsならNode.js公式ページからインストーラを落としてそれでインストールすればいいかと。

ii) TypeScriptをインストール

$ npm install -g typescript

Node.jsインストールでnpmも使えるようになっているはずなので、npmを使ってインストールします。もしかしたらsudoでやる必要があるかも。

iii) Angular CLIをインストール

$ npm install -g @angular/cli

こちらも、同様にnpmを使ってインストールします。これで、ngコマンドでAngularのコマンドラインツールを使えるようになります。

iv) プロジェクトを作成
例えば、AngularTest1という名前のプロジェクトを作成するとします。

$ ng new AngularTest1

これで、こんな感じのソースコードができます。
ここからさらに

$ ng serve --open

で、ローカルにサーバーが立ち上がり、こんな感じの画面が出てきます。

ただ、他のサイトとかだと
「Welcome to app!」
と出て、下に大きなAngularのアイコンが表示されてるのを見たけど、バージョンが違うのかな?もしくはどっかで操作を誤ったか(汗)

2.ソースの中身の簡単な解説

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>AngularTest1</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root>Loading...</app-root>
</body>
</html>
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
import { AppModule } from './app/app.module';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
}
<h1>
  {{title}}
</h1>

一番上のindex.htmlにapp-rootというものがありますが、これはコンポーネントといって、Vue.jsでもありましたが、1つの部品のようなものにするものです。
main.tsでapp/app.module.tsが呼ばれ、app/app.module.tsでapp/app.component.tsが呼ばれ、app/app.component.tsで実際のコンポーネントを定義するという流れになります。
なので、app/app.component.tsのtitleを変えれば、表示される文字列も変わってくることになります。

ここまでざっと書いて見たけど、次回はもう少し詳しく説明しようと思います。

LaravelとVue.jsで多言語対応を行う

今回は、LaravelとVue.jsを用いたプロジェクトで多言語対応を行うやり方を紹介します。
まず、resources/langに対応する言語のjsonファイルを置きます。とりあえず、日本語と英語を想定してja.json,en.jsonとします。

ja.json

{
    "こんにちは" : "こんにちは",
    "ありがとう" : "ありがとう",
    "さようなら" : "さようなら"
}

en.json

{
    "こんにちは" : "Hello",
    "ありがとう" : "Thank you",
    "さようなら" : "Good bye"
}

次に、Laravelのbladeで表示する方法です。

index.blade.php

<span>
    {{ __("こんにちは") }}
</span>

<span>
    @lang("ありがとう")
</span>

1つ目の方法は__ヘルパ関数を使用する方法です。翻訳文字列のキーを翻訳したものに変換するヘルパ関数です(Laravel標準のヘルパ関数なのでbladeだけでなく、モデルやコントローラでも使えます)。これで変換した文字列をMustache記法でechoします。
もう1つの方法はbladeの@langディレクティブを用いる方法です。このように2通りの書き方ができますが、どちらかに統一したほうがわかりやすいかもです。

次に、Vue.jsでの多言語対応の方法です。
vue-i18nというVue.jsのプラグインを使います。vue-i18nについての詳しい説明はこことかここを読んで下さい。
まず、

npm install vue-i18n

でvue-i18nをインストールします。
次に、こんな感じでvue-i18nをjs側で読み込みます。

main.js

window.Vue = require('vue');

import VueI18n from 'vue-i18n';
Vue.use(VueI18n);

// 言語の設定
Vue.use(VueI18n);
const i18n = new VueI18n({
    locale: locale,
    messages: {
        ja : require('../../lang/ja.json'),
        en : require('../../lang/en.json')
    }
});

import MainComponent from './components/MainComponent.vue';

const app = new Vue({
    i18n: i18n,
    components: {
        MainComponent
    }
});

MainComponent.vue

<template>
<div id="main-component">
    <span>
        {{ $t("さようなら") }}
    </span>
</div>
</template>

これで、Vue.jsで多言語表示ができるようになります。

ただ、1つ問題がありまして、vue-i18nでは第一階層のデータしか読み込めないので、

ja.json

{
    "果物一覧" : {
        "りんご" : "りんご",
        "みかん" : "みかん",
        "バナナ" : "バナナ"
    }
}

en.json

{
    "果物一覧" : {
        "りんご" : "apple",
        "みかん" : "orange",
        "バナナ" : "banana"
    }
}

のようなデータがこのままでは読み込むことが出来ないのです。
その場合は以下のように一旦blade側で加工してからコンポーネントに渡します。

index.blade.php

<script>
    var fruits_list = JSON.parse('{!! json_encode((__("果物一覧"))) !!}');
</script>

<main-component></main-component>

main.js

window.Vue = require('vue');

import VueI18n from 'vue-i18n';
Vue.use(VueI18n);

// 言語の設定
Vue.use(VueI18n);
const i18n = new VueI18n({
    locale: locale,
    messages: {
        ja : require('../../lang/ja.json'),
        en : require('../../lang/en.json')
    }
});

import MainComponent from './components/MainComponent.vue';

const app = new Vue({
    i18n: i18n,
    components: {
        MainComponent
    },
    data: function() {
        return {
            fruits_list : fruits_list
        }
    }
});

MainComponent.vue

<template>
<div id="main-component">
    <span v-for="fluit in fruits_list">
        {{ fluit }}
    </span>
</div>
</template>

自分はこんな感じのやり方でやりました(もしかしたらもっといいやり方もあるかもしれないので、他にいいやり方があれば教えていただければと思います)。

また、今回は言語切替機能については触れませんでしたが。そのあたりについてはこちらが参考になりました。

Axiosのファイル送信でハマった話

今月こそはVuexについてまとめようと思っていたのですが、Vuexについてはじっくり余裕を持って書けるときに書きたいということで、今回はAxios(Ajaxリクエストを送るためのpromiseベースのHTTPクライアント)でファイル送信を行うときにハマった話を書こうと思います。

今回やろうとしたのは、画像ファイルとかも含む編集画面で、例えばVue.jsでモーダルの編集フォームを作って、PATCHメソッドでAjaxで編集内容を送信するというケースでした。
まず、下記がうまくいかないケースです。

let url = '/send';
let data = new FormData();
data.append("file", file);
$('.btn').not('.vue_disabled').prop('disabled', true);
let self = this;
axios({'patch', url, data})
    .then(function (response) {
        //正常処理
    })
    .catch(function (error) {
        //エラー処理
    });

そしたらフォームが空になっていました(大汗)。
何故うまくいかないかわかりますか?
正解はaxiosのメソッドにはgetとかpostとかはあってもpatchというメソッドは用意されていないのです。
PHPがPOSTメソッド以外ではapplication/x-www-form-urlencodedやmultipart/form-dataで渡されたパラメタの処理をしてくれない仕様だから
です。(2018/9/9 一部修正、こちら参照)なので、どうしてもpatchで送りたいときは、下記のようにします。

let url = '/send';
let data = new FormData();
data.append("file", file);
data.append('_method', 'PATCH');
$('.btn').not('.vue_disabled').prop('disabled', true);
let self = this;
axios({'post', url, data})
    .then(function (response) {
        //正常処理
    })
    .catch(function (error) {
        //エラー処理
    });

axiosのメソッドとしてはpostだけど、送る時のデータにpatchメソッドであるという情報を含めるのです。
これで一安心です。

ちなみに、今回のはなかなか気が付かず、かなり悩んだのですが、色々ググってこちらのページに行き着いてようやくわかったというものです。
Laracasts様に本当に感謝。