Edited at

新幹線の移動時間でサービスを作ってリリースするまでの軌跡〜サクッと作るための技術スタックとは〜


まえがき

先週末、仕事で東京から岡山まで行く機会があり、新幹線の中で一人でハッカソンしてみたら、意外と0から作ってサービスを公開するところぐらいまではできました。当日の経過のログが誰かの助けになればと思い、投稿。TsuyoshiNumano/emojishare にコードは公開してます&要所要所で Pull Request を分けました。 サービスづくりの雰囲気だけでも感じ取ってもらえれば幸いです。

サービス自体は流行らないと思うので、そのうち消すと思います(ドメイン代とかもかかるし汗)

当日の twitter 実況


対象読者


  • さくっと web サービスを作って公開してみたい方

  • 割とフロントエンドよりの技術スタックなので、そのへんに興味ある方

*細かい技術的なところはお話しません。


使った技術スタック


  • create-react-app

    React でサクッとアプリ作るならオススメ


  • Google Apps Script & Google Spread Sheet

    貧者のためのデータベースとスクリプト。これなしでは生きていけない。


  • Firebase Hosting

    create-react-app との相性も良い。Google Domain でドメインを取って SPA をホスティングするのにちょうどよい。


  • material-ui

    かんたんにそれっぽい ui 作るならこれ。


  • Google Domains beta

    firebase とかで設定するのが楽



作ったもの

https://emojishare.app

Slack 絵文字の共有サービス


モチベーション


さっそく制作過程


1. 製作開始

品川駅からスタート


2. リポジトリを作成


3. おもむろに create-react-app

https://github.com/TsuyoshiNumano/emojishare/pull/1

yarn create react-app fron-react


4. List を表示するベースを作る

fron-react/src/App.js を編集


App.js


import React, { Component } from 'react';

class App extends Component {
constructor() {
super();
this.state = {
emojiList: [
{
url:
'https://emoji.slack-edge.com/T02HHLFPR/lgtm/393f650bcf7d2b0a.png',
likeCount: 0
}
]
};
}

render() {
return (
<div>
<div>emojishare</div>
<div>
{this.state.emojiList.map((emoji, i) => (
<img alt={emoji.url} key={'emoji' + i} src={emoji.url} width="20px" />
))}
</div>
</div>
);
}
}

export default App;


結果

https://github.com/TsuyoshiNumano/emojishare/pull/2

このへんで富士山が見えた。


5. データを入力して List に追加する


App.js

...

this.state = {
inputValue: ''
};
}

hadleChange = e => {
this.setState({ inputValue: e.target.value });
};

handleSubmit = e => {
this.setState({
emojiList: [
...this.state.emojiList,
{ url: this.state.inputValue, likeCount: 0 }
]
});
this.setState({ inputValue: '' });
};

render() {
return (
<div>
<div>emojishare</div>
<input
type="text"
placeholder="Please add your Emoji's url"
value={this.state.inputValue}
onChange={this.hadleChange}
/>
<button onClick={this.handleSubmit}>add</button>
<div>
{this.state.emojiList.map((emoji, i) => (
<img alt={emoji.url} key={'emoji' + i} src={emoji.url} width="20px" />
))}
</div>
</div>
);
...


https://github.com/TsuyoshiNumano/emojishare/pull/3/commits/5330e4334d5ff9e459789485c4555d76910addb1


6. GASで簡易 API サーバーを作る

WEB API としての公開などはこちらを参考に: 3分で API を作って世の中にデプロイするライブコーディング〜今日から君もスピードスターエンジニア〜


api_server.gs

function doGet(e) {

var response = {
data: [],
meta: { status: 'success' }
};

var sheet = SpreadsheetApp.getActiveSheet();
var sheetData = sheet.getRange('A2:B' + sheet.getLastRow()).getValues();

var responseList = [];

sheetData.map(function(d) {
responseList.push({ url: d[0], likeCount: d[1] });
});

response.data = responseList;

return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(
ContentService.MimeType.JSON
);
}


https://github.com/TsuyoshiNumano/emojishare/pull/3/commits/05d6deed7a2ad7887c28eda66368f5ff5dc120e5

DB


7. 簡易 API サーバーからのデータの取得

axios をつかって GAS の API にフロント側からリクエストを行う

yarn add axios


App.js

import axios from 'axios';

...

componentDidMount() {
axios
.get(
'https://script.google.com/macros/s/AKfycbyw9qQBpO7rm89iFWFjaslKcEqm4C72Z2smPh0rtcC68hMVGXI/exec'
)
.then(response => {
console.log(response);
this.setState({
emojiList: response.data.data
});
});
}

...


https://github.com/TsuyoshiNumano/emojishare/pull/3/commits/cd666ec40efee6a02e5c9e3c85c4a773a98ba4c8


8. API にデータを送り、フロントと連動するようにする


  • 入力したデータを API サーバーに送りつけて

  • スプレッドシートで作った DB に追加


api_server.gs

-  var sheetData = sheet.getRange('A2:B' + sheet.getLastRow()).getValues();

-
- var responseList = [];

- sheetData.map(function(d) {
- responseList.push({ url: d[0], likeCount: d[1] });
- });
+ if (e.parameter.url) {
+ sheet.appendRow([e.parameter.url, 0]);
+ }

+ var responseList = getData(sheet);
response.data = responseList;

return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(
ContentService.MimeType.JSON
);
}
+
+function getData(sheet) {
+ var sheetData = sheet.getRange('A2:B' + sheet.getLastRow()).getValues();
+
+ var responseList = [];
+
+ sheetData.map(function(d) {
+ responseList.push({ url: d[0], likeCount: d[1] });
+ });
+ return responseList;
+}



App.js

   handleSubmit = e => {

- this.setState({
- emojiList: [
- ...this.state.emojiList,
- { url: this.state.inputValue, likeCount: 0 }
- ]
- });
+ if (!this.state.inputValue) {
+ return;
+ }
+
+ axios
+ .get(
+ 'https://script.google.com/macros/s/AKfycbyw9qQBpO7rm89iFWFjaslKcEqm4C72Z2smPh0rtcC68hMVGXI/exec?url
+ encodeURI(this.state.inputValue)
+ )
+ .then(response => {
+ this.setState({
+ emojiList: response.data.data
+ });
+ });
this.setState({ inputValue: '' });
};

https://github.com/TsuyoshiNumano/emojishare/pull/4

ここで、 axios.post を使っていないのは CORS 問題でブラウザから GAS へは GET しかできないからです。

GETのクエリパラメータに無理やり送りたいデータをつけて送ってます。

名古屋あたり。


9. material-ui を入れて ui をリッチにする

yarn add @material-ui/core


App.js

import TextField from '@material-ui/core/TextField';

import Button from '@material-ui/core/Button';
import CircularProgress from '@material-ui/core/CircularProgress';

...

<div className="button-and-text">
<TextField
className="text-field"
type="text"
label="Slack Emoji url"
placeholder="Please add your Emoji's url"
value={this.state.inputValue}
onChange={this.hadleChange}
variant="outlined"
/>
<Button
onClick={this.handleSubmit}
variant="outlined"
color="primary"
>
add
</Button>
<div>{this.state.loadingFlag && <CircularProgress />}</div>
</div>

...


https://github.com/TsuyoshiNumano/emojishare/pull/5/


10. 簡単なバリデーションを入れる。

https://github.com/TsuyoshiNumano/emojishare/pull/6

京都に着。


11. Firebase にデプロイ

この辺を参考に Firebase Hosting でWebサイトを公開する方法

npm install -g firebase-tools

firebase init

ホスティングを選択して、deploy するフォルダを build にする。←地味に大事。

~/work/emojishare/front-react firebase init                 (git)-[feat/emojilist]

######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
###### ## ######## ###### ######## ######### ###### ######
## ## ## ## ## ## ## ## ## ## ##
## #### ## ## ######## ######## ## ## ###### ########

You're about to initialize a Firebase project in this directory:

/work/emojishare/front-react

? Which Firebase CLI features do you want to setup for this folder? Press Space to select features, then Enter to conf
irm your choices. Hosting: Configure and deploy Firebase Hosting sites

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

? Select a default Firebase project for this directory: [create a new project]

=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? build
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
? File build/index.html already exists. Overwrite? No
i Skipping write of build/index.html

i Writing configuration info to firebase.json...
i Writing project information to .firebaserc...

✔ Firebase initialization complete!

Project

ここまで来たらビルドしてデプロイする

yarn build

firebase deploy

https://github.com/TsuyoshiNumano/emojishare/pull/7


12. OGP を設定する

ogp 作るならこのサイトがオススメ

https://pablo.buffer.com

https://github.com/TsuyoshiNumano/emojishare/pull/8


13. Google Domain でドメインを取得する

こちらの記事がとてもわかり易いので割愛!

.appドメインはFirebase hostingとの相性が抜群


14. アイデアをもらったランキングシステムを実装

細かいので詳細は以下

https://github.com/TsuyoshiNumano/emojishare/pull/9


15. 完成

https://emojishare.app

ゴール!

最後ちょっと延長戦しましたが、なんとか品川から岡山までで、 API 通信するようなサービスを作って OGP を設定しドメインを取って公開するところまで行きました。


まとめ


  • 割とフロントエンド技術だけでウェブサービスを作ってさっさと公開できる。

  • 新幹線の移動時間でも簡単な WEB アプリは思ったより簡単に作れる!

  • ぱっと作るのは簡単だけど、このあとセキュリティとか、バリデーションとか、パフォーマンスを考え出すと、運用は色々工夫が必要。

今回のは react でつくりましたが、別に vue.js でも jQuery でもできると思います。(単に自分が最近この構成に慣れてるだけ)

みなさんも、是非オススメのプロトタイピングな開発スタックを教えてください。


Tips


  • 新幹線は窓側に電源ある。

  • できれば進行方向最前列だとちょっとスペースが広い。

  • この記事はだいたい帰りの新幹線で。

  • 東京から関西方面だと、神戸ぐらいにいくなら実は西明石までの往復きっぷの方が安い