从构建Docker开发环境到使用Laravel Nuxt.js部署Heroku


简介

在本文中,我想通过使用Laravel Nuxt.js构建"集合管理"系统来说明从0到1的"安装框架并将其发布到Internet"的阶段。

另外,实际构建的系统如下。
-Heroku:https://frozen-castle-47874.herokuapp.com/
-GitHub:https://github.com/kon-shou/bcm-qiita-example

Peek 2019-07-08 23-45.gif

目录

  • 系统架构
  • Laravel / Nuxt.js安装
  • Docker环境建设
  • Nginx设置
  • typescript兼容
  • 服务器上的模型/业务逻辑实现
  • 前面的模型/业务逻辑实现
  • Heroku设置
  • 系统架构

    使用以下技术堆栈。

    服务器端:Laravel 5.8
    正面:Nuxt.js 2.8.1(SPA)
    Web服务器:Nginx
    开发环境构建:docker
    部署:Heroku

    处理流程图如下所示。

    activity.png

    流程为"将Nuxt.js生成的SPA返回到浏览器,并通过Nginx从该SPA向API服务器Laravel发出API请求"。

    Laravel / Nuxt.js安装

    首先,根据Laravel文档使用Laravel创建一个项目。

    1
    composer create-project --prefer-dist laravel/laravel book-collection-management

    在您的Laravel项目中创建一个名为client的新目录,并根据Nuxt.js文档在其中安装Nuxt.js。

    1
    yarn create nuxt-app client ./client

    请在执行命令后根据您自己的要求设置选项。
    就我而言,我将其设置如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ? Project name client
    ? Project description My fantastic Nuxt.js project
    ? Author name kon-shou
    ? Choose the package manager Yarn
    ? Choose UI framework Buefy
    ? Choose custom server framework None (Recommended)
    ? Choose Nuxt.js modules Axios
    ? Choose linting tools ESLint, Prettier
    ? Choose test framework None
    ? Choose rendering mode Single Page App

    Nuxt.js是通过

    命令安装在./client上的,但是由于它实际上是对项目的双重管理,因此请执行以下操作。

    • 删除./client/node_module

      • rm -rdf ./client/node_modules
    • 取消管理./client的git

      • rm -rdf ./client/.git/
    • ./client中的各种配置文件移动并更新到laravel的根目录

    请根据您的环境更新设置文件。
    但是,请注意以下两个文件特别重要。

    • .package.json

      • 将nuxt命令添加到npm脚本
      • 将所需的库添加到nuxt
      • 修改后的.package.json
    • nuxt.config.js

      • 添加了srcDir: 'client/'

      • 将nuxt构建生成的index.html的输出目标更改为public/dist
      • 修改后的nuxt.config.js

    更改

    index.html的输出目标旨在放置在public下,它是文档的根目录。
    详细信息将在" Nginx设置"中进行说明。

    要更改index.html的输出目标,请遵循https://github.com/nuxt/nuxt.js/issues/3217

    nuxt.config.js

    1
    2
    3
      generate: {
        dir: 'public/dist'
      }

    您可以通过添加

    来更改输出目的地。

    执行上述操作后的目录结构如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    (root)
    ├── .editorconfig
    ├── .env
    ├── .env.example
    ├── .eslintrc.js
    ├── .git
    ├── .gitattributes
    ├── .gitignore
    ├── .idea
    ├── .prettierrc
    ├── .styleci.yml
    ├── app
    ├── artisan
    ├── bootstrap
    ├── client
    │   ├── README.md
    │   ├── assets
    │   ├── components
    │   ├── layouts
    │   ├── middleware
    │   ├── pages
    │   ├── plugins
    │   ├── static
    │   └── store
    ├── composer.json
    ├── composer.lock
    ├── config
    ├── database
    ├── node_modules
    ├── nuxt.config.js
    ├── package.json
    ├── phpunit.xml
    ├── public
    ├── readme.md
    ├── resources
    ├── routes
    ├── server.php
    ├── storage
    ├── tests
    ├── vendor
    ├── webpack.mix.js
    └── yarn.lock

    Docker环境构建

    接下来,使用Docker构建Web服务器和数据库,以便可以使用docker-compose up -d启动应用程序。
    要添加的文件如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    (root)
    ├── Dockerfile              => docker-compose.yml の app.build.dockerfile で参照される
    ├── docker
    │   ├── entrypoint-app.sh   => docker-compose.yml の app.command で参照される
    │   ├── nginx.conf          => docker-compose.yml の app.volumes で参照される
    │   └── php-fpm.conf        => docker-compose.yml の app.volumes で参照される
    ├── docker-compose.yml
    └── scripts
        └── provisioning.sh     => docker-compose.yml の app.command で参照される

    docker-compose.yml如下。

    docker-compose.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    version: '3'

    services:
      mysql:
        image: mysql:5.7
        volumes: # host/docker間で共有するデータを指定
          - "${HOME}/book-management_mysql:/var/lib/mysql"
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: 1
          TZ: "Asia/Tokyo"
        ports:
          - "3306:3306"
      app:
        build: . # Dockerfileのディレクトリを指定
        user: ubuntu
        volumes: # host/docker間で共有するデータを指定
          - .:/srv
          - ./docker/nginx.conf:/etc/nginx/sites-enabled/bcm
          - ./docker/php-fpm.conf:/etc/php/7.2/fpm/pool.d/bcm.conf
        command: docker/entrypoint-app.sh # 起動処理を設定
        depends_on:
          - mysql
        links:
          - mysql
        ports:
          - "8000:8000"
        working_dir: /srv

    docker-compose up -d的处理顺序如下。

  • 启动mysql容器
  • 启动应用程序容器

  • Dockerfile(provisioning.sh)创建docker映像

  • 挂载nginx.conf / php-fpm.conf

  • 开始entrypoint-app.sh nginx / php-fpm

  • 我将解释每个步骤。

    1.启动mysql容器

    mysql容器基于mysql5.7的映像,省略了密码并将时区设置为Tokyo,可以在主机/ docker的端口3306上对其进行访问。

    您可以使用mysql -h 127.0.0.01 -uroot

    主机访问mysql容器。
    您还可以使用docker-compose exec app bash从应用容器访问mysql,并使用mysql -h mysql -uroot进入应用容器。

    2.启动应用程序容器

    应用程序容器准备一个映像,该映像具有在Dockerfile中安装的必要库,将项目目录安装在/srv上,并使其可在主机/ docker的端口8000上访问。

    2.1从Dockerfile(provisioning.sh)

    创建docker映像

    Dockerfile如下。

    Docker文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    FROM ubuntu:18.04

    COPY scripts/provisioning.sh /tmp/provisioning.sh
    # provisioning.sh による必要ライブラリのインストール
    RUN /tmp/provisioning.sh

    # nginxの初期設定を削除
    RUN rm /etc/nginx/sites-enabled/default
    # php-fpmの初期設定を削除
    RUN rm /etc/php/7.2/fpm/pool.d/www.conf

    # ubuntuユーザーを追加
    RUN useradd -m -s /bin/bash -u 1000 -g users ubuntu

    RUN apt install sudo
    # ubuntuでのsudoのパスワード要求をしないように
    RUN echo "ubuntu ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers
    RUN chown ubuntu:users /srv

    Dockerfile中使用provisioning.sh安装该库,然后配置与用户相关的设置。
    provisioning.sh如下。

    Provisioning.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #!/usr/bin/env bash

    function package_install() {
      env DEBIAN_FRONTEND=noninteractive apt install -y $1
    }

    apt-get update -y

    package_install php-fpm
    package_install php-mysql
    package_install php-imagick
    package_install php-gd
    package_install php-curl
    package_install php-mbstring
    package_install php-bcmath
    package_install php-xml
    package_install php-zip
    package_install php-redis
    package_install php-intl
    package_install nginx
    package_install composer

    package_install mysql-client

    package_install zip
    package_install unzip
    package_install jq
    package_install git
    package_install jq
    package_install vim
    package_install curl

    如果您需要在应用容器中添加一个库,我认为将其添加到此provisioning.sh并再次构建图像??是一个好主意。

    2.2 nginx.conf / php-fpm.conf

    的安装

    通过放置这两个配置文件,可以正确启动nginx / php-fpm。

    php-fpm.conf如下。

    php-fpm.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [bcm]
    user = ubuntu
    group = users

    listen = /run/php/bcm.sock

    listen.owner = www-data
    listen.group = www-data

    pm = dynamic
    pm.start_servers = 1
    pm.max_children = 4
    pm.min_spare_servers = 1
    pm.max_spare_servers = 2

    request_terminate_timeout = 300

    chdir = /srv

    对于

    php-fpm.conf,我认为可以快速在官方手册中逐一检查选项。

    另外,文章https://qiita.com/kuni-nakaji/items/d11219e4ad7c74ece748非常容易理解,我以此为参考来了解套接字的外观。

    nginx.conf的详细信息将在下一个" Nginx设置"中进行说明。

    2.3为entrypoint-app.sh

    启动nginx / php-fpm

    entrypoint-app.sh如下。

    entrypoint-app.sh

    1
    2
    3
    4
    5
    6
    #!/bin/bash

    sudo service php7.2-fpm start
    sudo service nginx start

    tail -f /dev/null

    内容是php-fpm / nginx的启动和处理,以防止docker崩溃。

    通过将其作为docker启动过程进行操作,您可以仅通过docker-compose up -d启动php-fpm / nginx。

    有关tail -f /dev/null的更多信息,请参见http://kimh.github.io/blog/jp/docker/gothas-in-writing-dockerfile-jp/#hack_to_run_container_in_the_background上的博客文章,以供参考。到。

    Nginx设置

    通过安装

    Laravel / Nuxt.js,您现在可以准备index.html了,它将在您首次访问浏览器时返回给浏览器,而index.php将从该index.html接收API访问。

    因此,我想基于以下前提条件设置Nginx。

    • 文档根是/public
    • 将由nuxt build生成的index.html和从该index.html调用的js文件放在public/dist/

    • index.html返回到浏览器的API请求被路由到public/index.php

    满足此先决条件的

    nginx.conf如下。

    nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    server {
        # Nginxが待ちうけるポートを指定
        listen 8000 default_server;

        # ドキュメントルートを指定
        root /srv/public;

        # {URL}/ の場合に {URL}/index.html を返す
        # index index.html (デフォルト)

        # /{任意の文字列} に前方一致するURLの場合に
        # 1. /srv/public/{任意の文字列} に一致するファイルが存在すればそれを返し、存在しなければ
        # 2. /dist/index.html?$query_string にリダイレクトする
        location / {
            try_files $uri /dist/index.html?$query_string;
        }

        # {任意の文字列1}/_nuxt/{任意の文字列2}に一致するURLの場合に
        # 1. /srv/public/{任意の文字列1}/_nuxt/{任意の文字列2} に一致するファイルが存在すればそれを返し、存在しなければ
        # 2. /dist/_nuxt/{任意の文字列2} にリダイレクトする
        location ~ /(_nuxt)/(.+)$ {
            try_files $uri /dist/$1/$2;
        }

        # {任意の文字列1}/api/{任意の文字列2}に一致するURLの場合に
        # 1. /srv/public/{任意の文字列1}/api/{任意の文字列2} に一致するファイルが存在すればそれを返し、存在しなければ
        # 2. /index.php?$query_string にリダイレクトする
        location ~ /api/ {
            try_files $uri /index.php?$query_string;
        }

        # {任意の文字列}.phpに一致するURLの場合に
        # 1. /srv/public/{任意の文字列}.php に一致するファイルが存在すれば、そのリクエストをFastCGIに渡し、存在しなければ404を返す
        # 2. /index.php?$query_string にリダイレクトする
        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/run/php/bcm.sock;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }
    }

    实际访问的处理如下。

    <表格>

    这是哪种访问方式?

    Nginx是做什么的?


    <身体>

    1

    首次访问

    由于URL匹配/ ,因此将srv/public/dist/index.html返回浏览器

    2

    访问获取index.html <script>标记中所述的_nuxt/xxxx.js

    由于URL匹配/(_nuxt)/(.+)$ ,因此将srv/public/dist/_nuxt/xxxx.js返回浏览器

    3

    index.html 进行的API访问/api/xxx
    由于

    URL与/api/匹配,因此在Laravel中实现的响应会通过srv/public/index.php 返回到浏览器


    我认为理解此nginx.conf中的路由发生什么的关键如下。

    • 位置前缀匹配和正则表达式优先级之间的区别
    • try_files的行为

    https://heartbeats.jp/hbblog/2012/04/nginx05.html这篇文章非常容易理解,我将其用作参考。

    兼容typescript的

    并非总是必要的,但是就Nuxt.js而言,Typescript易于安装,因此我将同时安装它。

    您可以按照官方安装步骤进行安装。

    • 安装所需的库
    1
    2
    3
    yarn add -D @nuxt/typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser
    yarn add ts-node vue-class-component vue-property-decorator
    touch tsconfig.json
    • 修复

      nuxt.config.js

      • 添加了NuxtConfiguration

      • 重命名为nuxt.config.js

      • 修改的nuxt.config.ts
    • npm run dev

      之后修改tsconfig.json

      • 添加了"exclude": ["node_modules", "vendor"]

      • 固定为"baseUrl": "./client"

      • 修改后的tsconfig.json
    • 皮棉兼容

      • 修复parser / plugins

      • 修改后的.eslintrc.js
    • .vue兼容typescript的

      • 修改的index.vue(由于使用フロントでのモデル/ビジネスロジック実装创建了新页面/组件,因此无需在此处进行修改,仅供参考)

    如果执行

    及更高版本,则可以使用Typescript实现前端。

    服务器

    上的模型/业务逻辑实现

    在服务器上实现了以下5个。

    • 书本模型
    • 迁移文件
    • 图书库
    • 书本控制器
    • 路由
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    (root)
    ├── app
    │&nbsp;&nbsp; ├── Eloquent
    │&nbsp;&nbsp; │&nbsp;&nbsp; └── Book.php    => Bookモデル
    │&nbsp;&nbsp; ├── Http
    │&nbsp;&nbsp; │&nbsp;&nbsp; ├── Controllers
    │&nbsp;&nbsp; │&nbsp;&nbsp; │&nbsp;&nbsp; └── BookController.php  => Bookコントローラ
    │&nbsp;&nbsp; └── Repository
    │&nbsp;&nbsp;  &nbsp;&nbsp; └── BookRepository.php      => Bookレポジトリ
    ├── database
    │&nbsp;&nbsp; └── migrations
    │&nbsp;&nbsp;  &nbsp;&nbsp; └── 2019_06_12_150818_create_books_table.php    => migrationファイル
    └── routes
    &nbsp;&nbsp; └── api.php  => ルーティング

    另外,从前台来的用于书籍登记的API请求的流程如下。
    activity_server.png

    我将解释它们中的每一个。

    书籍模型

    Book.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?php

    namespace App\Eloquent;

    use Illuminate\Database\Eloquent\Model;

    /**
     * Class Book
     * @package App\Eloquent
     *
     * @property int $id
     * @property int $title
     */
    class Book extends Model
    {
    }

    假设书籍中仅存在" ID"和"标题",则定义书籍模型。
    在这种情况下,我认为最少的描述就足够了

    迁移文件

    数据库/迁移/ 2019_06_12_150818_create_books_table.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <?php

    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;

    class CreateBooksTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('books', function (Blueprint $table) {
                $table->bigIncrements('id');
                $table->string('title');
                $table->timestamps();
            });
        }

        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('books');
        }
    }

    由于

    图书模型是ActiveRecord,因此它具有与模型属性相似的列。

    图书库

    BookRepository.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <?php

    namespace App\Repository;

    use App\Eloquent\Book;
    use Illuminate\Support\Collection;

    class BookRepository
    {
        public function find(int $id): ?Book
        {
            return Book::query()->find($id);
        }

        public function list(): Collection
        {
            return Book::query()->get();
        }

        public function create(string $title): Book
        {
            $book = new Book();
            $book->title = $title;

            $book->save();

            return $book;
        }

        public function delete(Book $book)
        {
            return Book::query()->find($book->id)->delete();
        }
    }

    而不是直接在控制器中更新数据,而是更新存储库中的数据,以便控制器调用存储库中的方法。

    这样,具有诸如"如果更改保存数据的方式(例如:将Book的存储更改为NoSQL而不是RDS),则可以限制校正范围"之类的优点。

    书本控制器

    BookController.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <?php

    namespace App\Http\Controllers;

    use App\Repository\BookRepository;
    use Dotenv\Exception\ValidationException;
    use Illuminate\Http\Request;

    class BookController extends Controller
    {
        private $bookRepository;

        public function __construct(BookRepository $bookRepository)
        {
            $this->bookRepository = $bookRepository;
        }

        public function list()
        {
            return $this->bookRepository->list();
        }

        public function create(Request $request)
        {
            $title = $request->get('title');
            if (!$title) {
                throw new ValidationException('titleは必須です');
            }

            return $this->bookRepository->create($title);
        }

        public function delete(int $id)
        {
            $book = $this->bookRepository->find($id);
            if (!$book) {
                throw new ValidationException('削除対象のbookが存在しません');
            }

            $this->bookRepository->delete($book);

            return [];
        }
    }

    基本上,在

    控制器中,通过调用存储库的方法来获取/注册/删除Book模型。

    BookRepository还通过在构造函数中执行DI来简化控制器和存储库之间的依赖关系。
    这样,就有了诸如"在单元测试中,您可以调用专门准备的BookRepository,从而提高了编写单元测试的便利性"之类的优点。

    路由

    api.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?php

    use Illuminate\Http\Request;

    Route::group(['prefix' => 'book'], function ($route) {
        $route->get('/', 'BookController@list');
        $route->post('/create', 'BookController@create');
        $route->delete('/delete/{id}', 'BookController@delete');
    });

    为获取/注册/删除书籍准备三个端点,并从每个端点调用控制器的相应方法。

    确认

    请按照以下步骤检查数据库是否实际上已通过API请求进行了更新。

  • 更新.env

  • 将数据库添加到MySQL
  • 迁移执行
  • 在这种情况下,根据mysql容器的设置,如下所示更新.env

    .env

    1
    2
    3
    4
    5
    6
    DB_CONNECTION=mysql
    DB_HOST=mysql
    DB_PORT=3306
    DB_DATABASE=laravel
    DB_USERNAME=root
    DB_PASSWORD=

    更新

    .env后,将其作为php artisan config:cache来反映.env的变化。

    然后从应用程序容器(docker-compose exec app mysql -h mysql -uroot)连接到mysql

    1
    create database laravel;

    通过设置

    创建laravel数据库。

    我认为可以通过执行

    php artisan migrate来执行迁移。

    完成上述准备后,

    1
    curl http://localhost:8000/api/book/create -X POST -H "Content-Type: application/json" -d '{"title":"book_test"}'

    通过单击

    ,您可以看到更新已完成并返回了响应。

    前端模型/业务逻辑实现

    在前面实现以下6个。

    • 书本模型
    • 图书库
    • 插件/dependency.ts
    • 索引
    • Axios代理
    • 索引值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    (root)
    ├── client
    │&nbsp;&nbsp; ├── domain
    │&nbsp;&nbsp; │&nbsp;&nbsp; └── Book
    │&nbsp;&nbsp; │&nbsp;&nbsp;     ├── Book.ts
    │&nbsp;&nbsp; │&nbsp;&nbsp;     └── BookRepository.ts
    │&nbsp;&nbsp; ├── pages
    │&nbsp;&nbsp; │&nbsp;&nbsp; ├── index.vue
    │&nbsp;&nbsp; ├── plugins
    │&nbsp;&nbsp; │&nbsp;&nbsp; └── dependency.ts
    │&nbsp;&nbsp; └── static
    ├── index.d.ts
    └── nuxt.config.js

    前台的图书获取/注册流程如下。

    activity_front.png

    关于设计在前面有一个存储库层,尽管这是我的工作,但我将在这张幻灯片上进行详细说明,因此请参考它。

    书籍模型

    书本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import _ from 'lodash'

    export default class Book {
      constructor(protected properties: { [key: string]: any }) {}

      get id(): string {
        return _.get(this.properties, 'id')
      }

      get title(): string {
        return _.get(this.properties, 'title')
      }
    }

    定义类似于服务器的Book模型。
    这使您可以创建格式为new Book({id: xxx, titile: yyy})的模型,并在该模型上进行类型声明。

    图书库

    BookRepository.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import _ from 'lodash'
    import { AxiosInstance } from 'axios'
    import Book from '~/domain/Book/Book'

    export default class BookRepository {
      private axios: AxiosInstance

      constructor(axios: AxiosInstance) {
        this.axios = axios
      }

      public async listBooks(): Promise<Book[]> {
        const response = await this.axios.get('/book/')
        return _.map(response.data, bookData => new Book(bookData))
      }

      public async createBook(title: string): Promise<Book> {
        const response = await this.axios.post('/book/create', {
          title: title
        })
        return new Book(response.data)
      }

      public async deleteBook(book: Book) {
        await this.axios.delete(`/book/delete/${book.id}`)
      }
    }

    这也像服务器一样实现BookRepository。
    好处与服务器相同。

    axiso被注入到

    构造函数中,但这被注入到稍后描述的plugins/dependency.ts中。
    通过注入,您可以在保留axios上下文的同时发出API请求。

    插件/dependency.ts

    依赖

    1
    2
    3
    4
    5
    6
    7
    import BookRepository from '~/domain/Book/BookRepository'

    export default (context, inject) => {
      const bookRepository = new BookRepository(context.$axios)

      inject('bookRepository', bookRepository)
    }

    我已经将context.$axios传递给

    BookRepository,并将生成的bookRepository注册为$bookRepository,以便可以在Vue实例中调用它。

    nuxt.config.ts调用此插件。

    nuxt.config.ts

    1
      plugins: ['~/plugins/dependency'],

    关于Nuxt中的注入,官方文档和https://tech.cydas.com/entry/nuxt-inject上的文章非常容易理解,我已提及它。

    index.d.ts

    我在

    dependency.ts中注册了$bookRepository,但是如果保持原样,则会在Typescript检查中发生错误,因此将其添加到类型定义文件的$bookRepository中。

    索引

    1
    2
    3
    4
    5
    6
    7
    import BookRepository from '~/domain/Book/BookRepository'

    declare module 'vue/types/vue' {
      interface Vue {
        $bookRepository: BookRepository
      }
    }

    Axios代理

    我在

    BookRepository中使用axios,但是例如,如果我保持原样,则在注册一本书时,Nginx会向http://localhost:3000/book/create发出请求,而不是听http://localhost:8000/api/book/create,因此需要进行以下更正。

    • 指定axios.baseURL

    通过添加以下内容来添加/api作为url前缀。

    1
    2
    3
      axios: {
        baseURL: '/api'
      },
    • 使用@nuxtjs/proxy

    使用

    nuxt代理模块代理来自axios的请求。

    在这种情况下,将API服务器的URL添加到.env并将其调用为nuxt.config.ts

    首先,将以下内容添加到.env

    .env

    1
    API_BASE_URL=http://localhost:8000/api

    然后修改nuxt.config.ts

    nuxt.config.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import NuxtConfiguration from '@nuxt/config'

    require('dotenv').config()

    const config: NuxtConfiguration = {
    ...
      modules: [
        // Doc: https://buefy.github.io/#/documentation
        'nuxt-buefy',
        // Doc: https://axios.nuxtjs.org/usage
        '@nuxtjs/axios',
        '@nuxtjs/eslint-module',
        '@nuxtjs/proxy'
      ],
    ...
    }

    if (process.env.API_BASE_URL) {
      config.proxy = [process.env.API_BASE_URL]
    }

    export default config

    这将导致向http://localhost:8000/api发出axios请求。

    index.vue

    最后,修改index.vue。

    index.vue主要执行以下三个过程。

    • 使用asynData()运行$bookRepository.listBooks()以获取书籍列表
    • 单击注册按钮执行$bookRepository.createBook(this.title),注册书籍,然后重新获取列表。
    • 单击删除按钮执行$bookRepository.deleteBook(book),删除书籍,然后重新获取列表。

    索引值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    <template>
      <section>
        <table class="table">
          <tr>
            <th>ID</th>
            <th>タイトル</th>
          </tr>
          <tr v-for="book in books" :key="book.id">
            <td>{{ book.id }}</td>
            <td>{{ book.title }}</td>
            <td>
              <button
                type="button"
                class="button is-primary"
                @click.prevent="deleteBook(book)"
              >
                削除する
              </button>
            </td>
          </tr>
        </table>

        <section class="modal-card-body">
          <b-field label="本のタイトル">
            <b-input v-model="title" type="input" placeholder="タイトル" />
          </b-field>
          <button
            type="button"
            class="button is-primary"
            @click.prevent="createBook()"
          >
            登録する
          </button>
        </section>

        <p v-if="error">{{ error }}</p>
      </section>
    </template>

    <script lang="ts">
    import { Component, Vue } from 'vue-property-decorator'
    import Book from '~/domain/Book/Book'

    @Component({
      async asyncData({ app }) {
        const books = await app.$bookRepository.listBooks()

        return {
          books
        }
      }
    })
    export default class extends Vue {
      private books

      private title = ''
      private error = ''

      async createBook() {
        try {
          await this.$bookRepository.createBook(this.title)

          this.books = await this.$bookRepository.listBooks()
          this.title = ''
        } catch (e) {
          this.error = e
        }
      }

      async deleteBook(book: Book) {
        try {
          await this.$bookRepository.deleteBook(book)

          this.books = await this.$bookRepository.listBooks()
        } catch (e) {
          this.error = e
        }
      }
    }
    </script>

    确认

    在前面的前端/服务器上的实现完成后

    • docker-compose up -d
    • npm run dev

    如果执行

    ,则可以确认系统正在http://localhost:3000/上运行。

    Screenshot from 2019-07-09 00-22-41.png

    Heroku设置

    作为heroku设置,实现以下5

    • 添加infra/nginx/nginx.conf / Procfile

    • 添加heroku buildpack
    • 清除数据库设置
    • 修复app/Providers/AppServiceProvider.php

    • 设置环境变量
    1
    2
    3
    4
    5
    6
    7
    8
    (root)
    ├── Procfile
    ├── app
    │&nbsp;&nbsp; └── Providers
    │&nbsp;&nbsp;  &nbsp;&nbsp; └── AppServiceProvider.php
    └── infra
     &nbsp;&nbsp; └── heroku
     &nbsp;&nbsp;     └── nginx.conf

    假定

    heroku create与heroku的合作已经完成。

    infra/nginx/nginx.conf / Procfile

    的加法

    根据heroku的官方文档创建一个新的infra/nginx/nginx.conf以在heroku上使用Nginx。

    • 仅使用位置指令
    • fastcgi_pass指定heroku-fcgi

    如果反映

    的点,则如下所示。

    nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    location / {
        try_files $uri /public/dist/index.html?$query_string;
    }

    location ~ /_nuxt/(.+)$ {
        try_files $uri /public/dist/_nuxt/$1;
    }

    location ~ /api/ {
        try_files $uri /public/index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri /public/index.php =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass heroku-fcgi;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    另外,添加以下Procfile以使用上面的nginx.conf

    程序文件

    1
    web: vendor/bin/heroku-php-nginx -C infra/heroku/nginx.conf

    Procfile的作用在官方文档中进行了描述。

    添加heroku buildpack

    使用git push heroku master部署到

    heroku,但是添加一个buildpack来执行推后启动过程。

    1
    2
    heroku buildpacks:add heroku/nodejs
    heroku buildpacks:add heroku/php

    这将进行前端构建并安装所需的库。

    清除数据库设置

    我也想使用ClearDB在heroku上也使用mysql。

    首先,点击以下命令。

    1
    heroku addons:add cleardb

    然后变量CLEARDB_DATABASE_URL将设置为heroku config
    CLEARDB_DATABASE_URL可以如下读取。

    1
    mysql://{DB_USERNAME}:{DB_PASSWORD}@{DB_HOST}/{DB_DATABASE}?reconnect=true

    因此,通过如下所示在Heroku的环境变量中进行设置,您可以从laravel访问DB。

    1
    2
    3
    4
    heroku config:set DB_HOST={DB_HOST}
    heroku config:set DB_DATABASE={DB_DATABASE}
    heroku config:set DB_USERNAME={DB_USERNAME}
    heroku config:set DB_PASSWORD={DB_PASSWORD}

    通过上述操作,我希望Mysql连接完成,但是执行迁移时发生错误,可能是因为laravel假定的Mysql版本和Heroku的Mysql版本不同。

    因此,请进行以下更正。

    AppServiceProvider.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <?php

    namespace App\Providers;

    use Illuminate\Support\Facades\Schema;
    use Illuminate\Support\ServiceProvider;

    class AppServiceProvider extends ServiceProvider
    {
        /**
         * Register any application services.
         *
         * @return void
         */
        public function register()
        {
            //
        }

        /**
         * Bootstrap any application services.
         *
         * @return void
         */
        public function boot()
        {
            Schema::defaultStringLength(191);
        }
    }

    对于这种通信,我参考了https://qiita.com/beer_geek/items/6e4264db142745ea666f。

    确认

    完成上述准备后,

  • git push heroku master
  • heroku run php artisan migrate
  • 如果执行

    ,则可以确认它实际上在浏览器中正在工作。

    末尾

    通过以上内容,您已经成功地在Heroku上部署了用Lareavel Nuxt.js创建的Web应用程序。

    我认为文章的内容和要点中有很多错误,但是在这种情况下,我希望您在评论和编辑请求中指出。

    我希望本文对那些从Web开发的0到1阶段学习的人有所帮助。

    参考

    • https://readouble.com/laravel/5.8/ja/
    • https://ja.nuxtjs.org/
    • https://www.php.net/manual/ja/install.fpm.configuration.php
    • https://github.com/nuxt/nuxt.js/issues/3217
    • https://heartbeats.jp/hbblog/2012/04/nginx05.html
    • http://kimh.github.io/blog/jp/docker/gothas-in-writing-dockerfile-jp/#hack_to_run_container_in_the_background
    • https://tech.cydas.com/entry/nuxt-inject
    • https://devcenter.heroku.com/articles/custom-php-settings#using-a-custom-application-level-nginx-configuration
    • https://qiita.com/kuni-nakaji/items/d11219e4ad7c74ece748
    • https://qiita.com/beer_geek/items/6e4264db142745ea666f
    • https://speakerdeck.com/kon_shou/shi-yun-yong-niokerularaveltonuxtdefalserepositoryfalsereiyafen-ge-falsehua