はじめに
先日,Erlang & Elixir Fest 2019に参加し,とても刺激を受けたのでペーペーな私ですが記事を書いてみようと思ったので書きます.
下図に示すような感じで,Phoenix + Vue.jsでSPA(Single page application)をゼロから構成するお話です.チュートリアルですね.
Phoenixではjsonを返すAPIを作成し,vueからはaxiosを使ってAPIを叩いて表示します.
私自身がPhoenixもVueも始めてから日が浅いので,実際に作ってみた所感を記事に起こしたって感じです(笑)
ゼロから作るとか謳ってますが,phoenixの魔法や色々なライブラリに頼りまくりなので全然ゼロからではありません.
初心者向けくらいの意味でとらえていただけると幸いです.ご容赦ください🙇♂️
ここで作成するプロジェクトのソースコードはGitHubで公開しています.(絶賛更新中です...)
全てのコードを解説はできないので是非以下も参照してください.
sample_spa リポジトリ
プロジェクトの作成
今回は,掲示板システムを想定して作成します.
下図の様な感じです.1つの掲示板内には複数の投稿があるというだけです.
一応,実行環境は以下の通りです.
- macOS Mojave
- Elixir 1.8.1
- Phoenix 1.4.
- MySQL 5.7
API (Phoenix)の作成
それでは,みんな大好きmix phx.new
でPhoenixのプロジェクトを作成します.
プロジェクト名はsample_spa
とします.
$ mix phx.new sample_spa --database mysql
MySQLの設定
ここは各々によりますが,ユーザーやパスワードの設定をしておく場合はしておきましょう.
MySQLの設定はconfig/dev.exs
に記述します.
適宜,username
やpassword
を設定します.
# Configure your database
config :sample, Sample.Repo,
username: "root",
password: "",
database: "sample_spa_dev",
hostname: "localhost",
pool_size: 10
ここまで完了したら,dbのセットアップをしてみましょう.
$ mysql.server start
$ mix ecto.setup
MySQLにsample_spa_dev
というデータベースが作成されていれば問題ないです🙆♂️
modelの作成
続いてmodelの作成をします.
今回のboardとpostの2つのモデルは掲示板のお話なので,bbsというコンテキストで定義します.
boardの作成
boardモデルは以下のような要素を持っています.
board | |
---|---|
id | mysql側でauto_increment |
title | 掲示板のタイトル |
description | 掲示板の概要 |
あんまり魔法は使いたくないのですがboardモデルの作成にはmix phx.gen.json
という魔法を使わせていただきます🙇♂️
$ mix phx.gen.json Bbs Board boards title:string description:string
色々とファイルが作成されますが,注目すべきファイルの簡単な説明を以下に載せます.
-
lib/sample_spa/bbs/board.ex
... 作成したモデルの定義 -
lib/sample_spa/bbs.ex
... bbsコンテキストのビジネスロジックを記述 -
lib/sample_spa_web/controllers/board_controller.ex
... リクエストに対応したビジネスロジックを呼び出す -
lib/sample_spa_web/views/board_view.ex
... レスポンスの整形を行う
ecto.migrate
コマンドでdbにboardsテーブルを作成しましょう.
$ mix ecto.migrate
MySQLにboards
テーブルができてれば問題ないです🙆♂️
postの作成
post | |
---|---|
id | mysql側でauto_increment |
body | 投稿内容 |
board_id | 掲示板のid |
postモデルの作成にはmix phx.gen.schema
で作成します.
$ mix phx.gen.schema Post posts body:string
lib/sample_spa/post.ex
とpriv/repo/migrations/~_create_posts.exs
が作成されます.
postもbbsコンテキストのmodelなのでわかりやすいようにlib/sample_spa/bbs
ディレクトリに移動しておきましょう.
$ mv lib/sample_spa/post.ex lib/sample_spa/bbs
移動したら,defmoduleの部分を変更しておきましょう.
defmodule SampleSpa.Post do # 変更前
defmodule SampleSpa.Bbs.Post do # 変更後
boardとpostの関係を追加
boardとpostが一対多の関係であることを追記します.
まずはboard.ex側にpostを複数持つことを記述します.
schema "boards" do
field :description, :string
field :title, :string
# postとの関係
has_many :posts, SampleSpa.Bbs.Post, on_delete: :delete_all
timestamps()
end
次にpost.ex側にboardを一つ持つことを記述します.
schema "posts" do
field :body, :string
# boardとの関係
belongs_to :board, SampleSpa.Bbs.Board, foreign_key: :board_id
timestamps()
end
最後にマイグレーションファイルに外部キーのboard_id
を加えておきましょう.
create table(:posts) do
add :body, :string
# board_idを追加する
add :board_id, references(:boards, on_delete: :delete_all), null: false
timestamps()
end
ここまでしたら,ecto.migrate
コマンドでdbにpostsテーブルを作成しましょう.
$ mix ecto.migrate
MySQLにposts
テーブルができてれば問題ないです🙆♂️
ルーティングの設定
ルーティングの設定の指針を先に示しておきます.
今回は以下の表に示すようなエンドポイントになります.
path | httpメソッド | 内容 |
---|---|---|
/api/boards | GET | 掲示板を全件取得 |
/api/boards | POST | 掲示板を新規作成 |
/api/boards/:id | GET | 指定した掲示板内の投稿を全件取得 |
/api/boards/:id | POST | 指定の掲示板内に新規投稿を作成 |
lib/sample_aps_web/route.ex
に記述を追加してルーティングの設定を行います.
まずは掲示板の全件取得と掲示板を新規作成するルーティングを追加しましょう.
scope "/api", SampleSpaWeb do
pipe_through :api
scope "/boards" do
get "/", BoardController, :index
post "/", BoardController, :create
get "/:id", BoardController, :show
post "/:id", BoardController, :create_post
end
end
ルーティングの設定ができてるか確認しましょう.
$ mix phx.routes
board_path GET /api/boards SampleSpaWeb.BoardController :index
board_path POST /api/boards SampleSpaWeb.BoardController :create
board_path GET /api/boards/:id SampleSpaWeb.BoardController :show
board_path POST /api/boards/:id SampleSpaWeb.BoardController :create_post
page_path GET / SampleSpaWeb.PageController :index
websocket WS /socket/websocket SampleSpaWeb.UserSocket
ちゃんとできてますね!
postの新規作成,全件取得の処理の作成
boardの作成・取得の処理は自動生成されていますが,postに関してはないので追加してきます.
viewの用意
最初に,postモデルをレスポンスでjsonとして返すときのviewを用意しておきましょう.
$ touch lib/sample_spa_web/views/post_view.ex
defmodule SampleSpaWeb.PostView do
use SampleSpaWeb, :view
alias SampleSpaWeb.PostView
def render("index.json", %{posts: posts}) do
%{data: render_many(posts, PostView, "post.json")}
end
def render("show.json", %{post: post}) do
%{data: render_one(post, PostView, "post.json")}
end
def render("post.json", %{post: post}) do
%{id: post.id,
body: post.body}
end
end
bbs.exにビジネスロジックの記述
lib/sample_spa/bbs.ex
を編集して,dbにpostを登録する処理とdbから全件取得する処理を追加します.
はじめにaliasにpostを追加してきます.
alias SampleSpa.Bbs.Board
# postを追加
alias SampleSpa.Bbs.Post
次に処理を追加しましょう.
# 掲示板に投稿する
def create_post(board_id, attrs \\ %{}) do
board = get_board!(board_id)
%Post{}
|> Post.changeset(attrs)
|> Ecto.Changeset.put_assoc(:board, board)
|> Repo.insert()
end
# 掲示板内の投稿を全て取得する.
def get_posts_by_board_id!(board_id) do
from(p in Post,
where: p.board_id == ^board_id
)
|> Repo.all()
end
controllerに処理を追加
最後にcontrollerを編集します.
先ほどと同様にaliasにpostを追加してきます.
alias SampleSpa.Bbs.Board
# postを追加
alias SampleSpa.Bbs.Post
次に,show関数で先ほどのbbs.exのget_posts_by_board_id!
を呼び出すように編集します.
# 掲示板の投稿を全件取得
def show(conn, %{"id" => board_id}) do
posts = Bbs.get_posts_by_board_id!(board_id)
conn
|> put_view(SampleSpaWeb.PostView)
|> render("index.json", posts: posts)
end
# 掲示板に投稿する
def create_post(conn, %{"id" => id, "post" => post_params}) do
board = Bbs.get_board!(id)
with {:ok, %Post{} = post} <- Bbs.create_post(board, post_params) do
conn
|> put_status(:created)
|> put_view(SampleSpaWeb.PostView)
|> render("show.json", post: post)
end
end
APIの確認
では実際にサーバを立ち上げてリクエストを送ってきちんと処理できるか確認しましょう.
$ mix phx.server
boardの登録と取得
curlで掲示板の情報をjsonを送ってみましょう.
$ curl -XPOST -H "Content-type: application/json" -d '{"board": { "title": "テスト", "description": "テスト用の掲示板です." }}' http://localhost:4000/api/boards
{"data":{"description":"テスト用の掲示板です.","id":15,"title":"テスト"}}%
次に全件取得してみましょう.
$ curl http://localhost:4000/api/boards
{"data":[{"description":"テスト用の掲示板です.","id":1,"title":"テスト"}]}%
取得できました.これで登録できていることがわかります.
postの登録と取得
boardと同様にcurlで登録して,取得します.
$ curl -XPOST -H "Content-type: application/json" -d '{"post": { "body": "テスト登録"}}' http://localhost:4000/api/boards/7
"data":{"body":"テスト登録","id":1}}%
$ curl http://localhost:4000/api/boards/1
{"data":[{"body":"テスト登録","id":1}]}%
SPA(Vue)の作成
ここからが本番ですね.Vueを使ってSPAを作成します.
Phoenix 1.4からはassets配下はwebpackなのでそちらを利用します.
assetsディレクトリに移動して以下のライブラリをインストールします.
- Vue ... jsフレームワーク
- Vue-loader ... vueを使うためのやつ
- vue-template-compiler ... vueを使うためのやつ
- Vuex ... 状態管理
- Vue-router ... SPAのルーティング制御
- axios ... APIと通信するHTTPクライアント
- Buefy ... cssフレームワーク/Vue専用
上3つはVueを使ってSPAを実現するための基本ライブラリだと思ってください.
$ cd assets
$ npm install vue vue-loader vue-template-compiler vuex vue-router axios buefy
vueファイルを読み込んでくれるようにwebpack.config.js
を修正します.
初めのconstがたくさんあるとこに以下を追加します.
const path = require('path');
...省略...
// 追加
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = (env, options) => ({
...省略
module: {
rules: [
...省略
//追加
{
test: /\.vue$/,
loader: 'vue-loader'
},
...省略
],
},
//追加
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
vue$: 'vue/dist/vue.esm.js',
}
},
plugins: [
new MiniCssExtractPlugin({ filename: '../css/app.css' }),
new CopyWebpackPlugin([{ from: 'static/', to: '../' }]),
//追加
new VueLoaderPlugin()
]
});
次にassets配下にVueのプロジェクトを構築するディレクトを配置します.
$ mkdir js/src
$ cd js/src
$ mkdir components router store
$ touch main.js App.vue
$ tree .
.
├── App.vue //Vue単一コンポーネントファイル
├── components //コンポーネントを配置するディレクトリ
├── main.js //vueの設定を行う
├── router //vue-router用のディレクトリ
└── store //vuex用のディレクトリ
まずはvueを読み込めているか確認したいので,App.vue
とmain.js
を編集します.
App.vueはただHello, Vue.js!
と表示するだけです.
<template>
<div>
{{ text }}
</div>
</template>
<script>
export default {
name: 'app',
props: {
text: {
type: String,
default: 'Hello, Vue.js!'
}
}
}
</script>
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
「なんとなくわかった.じゃあどうやってこのApp.vueを呼び出すの?」
そうですよね,Vue側を呼び出せるようにVueとPhoenixの連携の設定をしてきます.
VueとPhoenixの連携
VueとPhoenixをどうやって連携させるか.
答えはlib/sample_spa_web/controllers/page_controller
とtemplates/layout/app.html.eex
を用います.
まずpage_controllerへのルーティングに少し修正を加えます.編集前はコメントアウトしておきます.
scope "/", SampleSpaWeb do
pipe_through :browser
# get "/", PageController, :index
get "/*path", PageController, :index
end
これはroute.ex
の終わりに書きます.これによって任意のリクエストをpage_cntroller
で受け取ります.
page_controller
のコードを確認してみましょう.
defmodule SampleSpaWeb.PageController do
use SampleSpaWeb, :controller
def index(conn, _params) do
render(conn, "index.html")
end
end
page_controllerは,lib/sample_spa_web/templates/page/index.html.eex
を呼び出しています.
そしてそのレイアウトを定義しているのが,lib/sample_spa_web/templates/layout/app.html.eex
になります.
そこでapp.html.eex
を編集して,Vue側を呼び出すように編集します.
<body>タグ内を全て書き換えます.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>SampleSpa · Phoenix Framework</title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
</head>
<%# 変更箇所 %>
<body>
<div id="app"></div>
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</body>
</html>
さっきまで編集してたjs/src/main.js
を指定しないの?と言われそうですが,これには理由があります.
assets配下のwebpack.config.js
を見るとわかります.
module.exports = (env, options) => ({
...省略...
entry: {
'./js/app.js': ['./js/app.js'].concat(glob.sync('./vendor/**/*.js'))
},
webpackのエントリポイントはデフォルトではjs/app.js
になっています.
なのでwebpack側にはja/app.js
を通してしかアクセスできません.
よって,js/app.js
を編集してjs/src/main.js
をインポートします.
// import "phoenix_html"
import "./src/main.js"
ここまでできたら,アプリを起動します.
$ mix phx.server
http://localhost:4000 にアクセスして以下のように見えていれば成功です.
これでPhoenixとvueの連携ができました👏
Componentsの作成
ここからは完全にVueのお話です.
Vueではコンポーネントベースに開発を進めて行きます.
事前にコンポーネントの分割ルールを作っておくことが重要となります.
今回は,Atomic Designを意識したコンポーネントの分割を考えます.
まずはディレクトリを作りましょう.
$ cd assets/js/src/components
$ mkdir atoms molecules organisms
cssフレームワークのBuefyを使うことも考慮して,以下のようなルールで今回はコンポーネントを作成していきます.
- atoms ... Buefyで提供されるデザインを細かくコンポーネント化したものを配置する.
- molecules ... atomsのコンポーネントを組み合わせて作成する.(ただし状態管理を担うvuexとやりとりしてはいけない)
- organisms .... atoms, moleculesのコンポーネントを組み合わせて作成する.(ここでvuexとやりとりする)
ここら辺は実際に作ってみてわかる部分でもあるので進めてみましょう.
まずはBuefyのインポートの設定をmain.jsに追記します.
import Buefy from 'buefy'
import 'buefy/dist/buefy.css'
Vue.use(Buefy)
ちなみにBuefyはBulmaをVue向けにしたフレームワークであり,Bulmaを包括しています.
なのでBulmaのデザインを全て使えるという利点があります.
(実はBulmaを使用したかったのですが,webpackでの設定が上手く行かなかったの代用しています...)
atoms
BulmaのHPで紹介されてるcardをコンポーネントをに分けて作成してみましょう.
赤枠で囲っている部分をatomsのコンポーネントとして構築します.
全体をTheCard.vue
,中身をTheCardContent.vue
,下のボタンをTheCardFooterItem.vue
として作成します.
<template>
<div class="card">
<header class="card-header">
<p class="card-header-title">
{{ title }}
</p>
</header>
<div class="card-content">
<slot name="content" />
</div>
<footer class="card-footer">
<slot name="footer"/>
</footer>
</div>
</template>
<script>
export default {
name: 'TheCard',
props: {
title: {
type: String,
default: 'タイトル'
}
}
}
</script>
TheCard.vue
ではcardコンポーネントの概形を定義する感じです.
<slot/>
を使うことで他のコンポーネントを入れ子にすることができます.
<template>
<div class="content">
{{ body }}
</div>
</template>
<script>
export default {
name: 'TheCardContent',
props: {
body: {
type: String,
default: 'コンテンツ'
}
}
}
</script>
TheCardContent.vue
はテキストコンテンツを表示するだけです.
<template>
<a @click="click" class="card-footer-item">
{{ text }}
</a>
</template>
<script>
export default {
name: 'TheCardFooterItem',
props: {
text: {
type: String,
default: 'リンク'
}
},
methods: {
click() {
this.$emit('click')
}
}
}
</script>
TheCardFooterItem.vue
は<a>
タグでリンクの働きをします.
atomsではクリックされたときの挙動の制御は行いません.
this.$emit('click')
でコンポーネント内の処理を,他のコンポーネントに引き継ぐことができます.
molecules
作成したatomsのコンポーネントを組み合わせて,掲示板の情報を表示するTheBoard.vue
と名前のコンポーネントを作成します.
<template>
<the-card :title="board.title">
<the-card-content
slot="content"
:body="board.discription"
/>
<the-card-footer-item
slot="footer"
text="Opne"
@click="click(board.id)"
/>
</the-card>
</template>
<script>
import TheCard from '../atoms/TheCard'
import TheCardContent from '../atoms/TheCardContent'
import TheCardFooterItem from '../atoms/TheCardFooterItem'
export default {
name: 'TheBoard',
components: {
TheCard,
TheCardContent,
TheCardFooterItem
},
props: {
board: {
type: Object,
default() {
return {
id: 1,
title: '掲示板',
discripition: 'サンプルです.'
}
}
}
},
methods: {
click(id) {
this.$emit('click', id)
}
}
}
</script>
このようにコンポーネントベースでUIを組み立てて行きます.
organismsの話に移る前にVueでのデータの永続化を担当するvuexの設定を行います.
vuex
最初に作ったPhoenixのAPIを叩いて受け取ったレスポンスをvuexを使って保持します.
なおAPIを叩くにはaxiosというライブラリを使用します.
まずはvuexとaxiosを使う準備をしましょう.
storeディレクトリ配下にindex.js
とmodules
ディレクトリを作成しましょう.さらにmodules
ディレクトリにはbbs.js
というファイルを配置しましょう.
$ cd assets/js/src/store
$ mkdir modules
$ touch index.js modules/bbs.js
import Vue from 'vue'
import Vuex from 'vuex'
// modules
import bbs from './modules/bbs'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
bbs
}
})
export default store
store/index.js
はvuexの設定を記述します.
import Vue from 'vue'
export default {
namespaced: true,
state: {
},
getters: {
},
mutations: {
},
actions: {
}
}
moudles/bbs.js
はPhoenixのbbsコンテキストのモデルを保持するように設計します.
これから追記してきますが,bbs.jsにはstate
,getters
,mutations
,actions
の4つの部分から成ります.
それぞれの役割は下図がわかりやすいと思います.(図はvuex公式より引用)
getters
は?となりそうですが,getters
はVueコンポーネントからstateの値を参照するのに用います.図のRenderに相当します.
では,modules/bbs.js
で掲示板のデータを取得できるように編集していきましょう.
import Vue from 'vue'
export default {
namespaced: true,
state: {
boards: []
},
getters: {
boards: state => state.boards
},
mutations: {
setUser(state, { boards }) {
state.boards = boards
}
},
actions: {
async getBoards({ commit }) {
await Vue.axios.get('/api/boards')
.then(res => {
commit('setBoards', {
boards: res.data
})
})
.catch(err => {
throw err.response.status
})
}
}
}
重要となるのはactionsで部分でしょうか.axiosを使ってAPIを叩きます.
async await
はは非同期処理です..then
からでAPIサーバからのレスポンスを受け取りmutationsを呼び出します.
catch
は例外を処理します.つまりAPIサーバとの通信で何かしら失敗した場合の処理です.
「vuexの使い方のパターンはわかった.じゃあどうやって使うんだ?」
そうですよね,次はvuexとやりとりを実際に行うorganismsのコンポーネントを作成していきましょう.
organisms
organismsではatoms, moleculesのコンポーネントを組み合わせて作成します.
今回はvuexで取得したboardsを全て描画するTheBoardList.vue
コンポーネントを作成します.
一応,作成の指針としては,コンポーネントを読み込んだタイミングでAPIにアクセスし,その結果をimportしたmolculetsのコンポーネントに流し込みます.
<template>
<div >
<the-board
v-for="board in boards"
:key="board.id"
:board="board"
/>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import TheBoard from '../molecules/TheBoard'
export default {
name: 'TheBoardlist',
components: {
TheBoard
},
computed: {
...mapGetters({
boards: 'bbs/boards'
})
},
methods: {
...mapActions({
getBoards: 'bbs/getBoards'
})
},
created() {
this.getBoards().catch(() => {
console.error('Vuex Error')
})
}
}
</script>
vuexとのやりとりはヘルパー関数mapGetters
,mapActions
を用います.
- mapGetters ... storeのgettersを呼び出す.computedに記述する.
- mapActions ... storeのactionsを呼び出す.methodsに記述する.
このヘルパー関数を用いることでgettersやactionsに別名をつけられる上に,methodsはコンポーネント内でthis
を使って呼び出すことができます.
created()
はコンポーネントが呼び出されてはじめに呼び出されるメソッドです.ここでactionsを呼び出しvuex側でAPIと通信が行われます.
まあ論より証拠ってことで動かすのが早いですよね😅
このコンポーネントをApp.vueにimportして表示してみましょう.
<template>
<div>
{{ text }}
<the-board-list/>
</div>
</template>
<script>
import TheBoardList from './components/organisms/TheBoardList'
export default {
name: 'app',
components: {
TheBoardList
},
props: {
text: {
type: String,
default: 'Hello, Vue.js!'
}
}
}
</script>
この状態で実行してみます.
$ mix phx.server
あら,真っ白でエラー吐かれちゃいました.
どうやらbabelでasync awaitを使用するには設定を加える必要があるようです.
babelでasync/awaitを使えるように修正
以下の記事を参考に修正を加えます.
babelでasyncを使おうとしたらregeneratorRuntime is not definedが出た
{
"presets": [
[
"@babel/preset-env", {
"targets": {
"node": "current"
}
}
]
]
}
問題なく起動できました.データは適当に加えてある状態になってます.
ちなみにchromeにVue.js devtoolsを入れておくと開発者は幸せになれます.入れておきましょう.
Vue-routerの導入
レイアウトを整えつつ,Vue-routerを導入します.
Vue-routerは簡単に言ってしまえば,ルーティングによって描画するコンポーネントを書き換えるものです.
まずはVue-routerを使う準備をしましょう.assets/js/src/router
ディレクトリ配下にindex.jsを置きます.
$ touch assets/js/src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
// components
import TheBoardList from '../components/organisms/TheBoardList'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
component: TheBoardList,
}
]
})
export default router
ここで設定しているのは,パスとそれに対応するコンポーネントです.
ここでは/
にTheBoardList
を割り当てています.
次にassets/js/src/main.js
にこのrouterの設定をインポートします.
// importにrouterを追加
import router from './router'
...省略
new Vue({
el: '#app',
store,
router, // 追加
template: '<App/>',
components: { App }
})
これでVue-routerを使う準備ができました.
ではasstes/js/src/App.vue
にVue-routerを組み込んでみましょう.ついでにレイアウトも少し整えて置きます.
<template>
<div class="hero">
<div class="hero-body">
{{ text }}
<router-view/>
</div>
</div>
</template>
<script>
export default {
name: 'app',
props: {
text: {
type: String,
default: 'Hello, Vue.js!'
}
}
}
</script>
<router-view/>
の部分で描画されるコンポーネントがパスに応じて変化します.
実際に起動してみましょう.
$ mix phx.server
上手くいってますね!
無事,Vue-routerが導入できました.
あとやらなければならないこととしては,
- 投稿を全て描画するコンポーネントの作成
- 掲示板を新規作成するFormを含むコンポーネントの作成
- 掲示板に投稿するFormを含むコンポーネントの作成
が残ります.
これらは後日,追記していきます🙇♂️
まとめ
この記事の扱ったトピックのまとめです.
- Phoenixでjsonを返すAPIの作成
- リレーションシップを持ったモデル構造
- phooenixのルーティング制御でVueのプロジェクトに繋ぐ
- Vue.jsを使ったSPAの作成
- コンポーネントベースのSPAの設計
- axiosによるAPIアクセスとvuexによる状態管理
- Vue-routerによるSPAのルーティング
おわりに
最初にも述べましたが,Erlang & Elixir Fest 2019に参加して熱量が上がったので,とにかく冷めないうちに記事を書こうと思って書きました.
全て完成してから,記事を出そうと思っていたのですが先に公開して更新していくことにしました.
未完成の部分はこれから更新していきます,申し訳ありません🙇♂️