gabakugik
@gabakugik (GABAKU GIK)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Vueでrails apiのデータを表示させたい。

解決したいこと

APIでたたいたのをVueで表示させたい。

できました。ありがとうございました。

発生している問題・エラー

[Rails+Vue.js]~Bookshelf~だけ表示される。
データが表示されない。

階層

├── backend
│   ├── app
│   ├── bin
│   ├── config
│   ├── db
│   ├── lib
│   ├── log
│   ├── public
│   ├── storage
│   ├── test
│   ├── tmp
│   └── vendor
└── frontend
    └── vite-project

やったこと

docker-compose run backend bundle exec rails g model Book title:string author:string publisher:string genre:string
app/models/book.rb
class Book < ApplicationRecord
  validates :title, presence: true, length: { maximum: 100 }
  validates :author, presence: true, length: { maximum: 100 }
  validates :publisher, presence: true, length: { maximum: 50 }
  validates :genre, presence: true, length: { maximum: 50 }
end

docker-compose run backend rails db:migrate
gem 'faker'
gem 'rack-cors'
docker compose run backend bundle install

docker-compose build
config/seeds.rb
20.times do
  Book.create!(
    title: Faker::Book.title,
    author: Faker::Book.author,
    publisher: Faker::Book.publisher,
    genre: Faker::Book.genre
  )
end
docker-compose run backend rails g controller api::books
config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    resources :books
  end
end
app/controllers/api/books_controller.rb
class Api::BooksController < ApplicationController
  def index
    books = Book.all

    render json: books

    # 質問主さんの書き方の場合、フロント側はbooks.value = res.data.bookで受け取る
    # render json: {book: books}
  end

  def show
    book = Book.find(params[:id])
    render json: book
  end
end
backend/config/initializers/cors.rb
 Rails.application.config.middleware.insert_before 0, Rack::Cors do
   allow do
     origins "localhost:5173"
     resource "*",
       headers: :any,
       methods: [:get, :post, :put, :patch, :delete, :options, :head]
   end
 end
frontend/vite-project/src/lib/axios.ts
import axios from 'axios'

export default axios.create({
    baseURL: 'http://localhost:3000',
})
main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')
package.json
{
  "name": "vite-project",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.4.0",
    "bootstrap": "^5.3.1",
    "vue": "^3.3.4",
    "vue-class-component": "^7.2.6",
    "vue-property-decorator": "^9.1.2",
    "vue-router": "^4.2.4"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.2.3",
    "typescript": "^5.0.2",
    "vite": "^4.4.5",
    "vue-tsc": "^1.8.5"
  }
}
App.vue

<template>
  <div class="container">
    <h1 class="#f3e5f5 purple lighten-5 center">[Rails+Vue.js]~Bookshelf~</h1>
    <div class="row #e3f2fd blue lighten-5">
      <div class="col s4 m6" v-for="book in books" :key="book.id">
        <div class="card btn">
          <span class="card-title" @click="setBookInfo(book.id)">
            {{ book.title }}
          </span>
        </div>
      </div>
    </div>
    <div class="row" v-show="bookInfoBool">
      <div class="col s12 m12">
        <div class="card blue-grey darken-1">
          <div class="card-content white-text">
            <span class="card-title">{{ bookInfo.title }}</span>
            <div class="detail">
              ・著者:{{ bookInfo.author }}
            </div>
            <div class="detail">
              ・出版社:{{ bookInfo.publisher }}
            </div>
            <div class="detail">
              ・ジャンル:{{ bookInfo.genre }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import axios from 'axios';

const bookInfo = ref({});
const bookInfoBool = ref(false);
const books = ref([]);

const fetchBooks = () => {
  axios.get('http://localhost:3000/api/books').then(
    (res) => {
      books.value = res.data;
    },
    (error) => {
      console.log(error);
    }
  );
};

const setBookInfo = (id: number) => {
  axios.get(`http://localhost:3000/api/books/${id}.json`).then((res) => {
    bookInfo.value = res.data;
    bookInfoBool.value = true;
  });
};

onMounted(fetchBooks);
</script>

<style scoped></style>

自分で試したこと

いろいろパッケージをyarn addを使って入れてみた。よくわからず....
よろしくお願いします。
ここの部分の情報はありますか?
とかありましたら記載します。

アドバイスをもらい、一件rails cで入力してデータベースまでは反映されましたが
web上には表示されないです。

frontend/vite-project/src/lib/axios.ts
import axios from 'axios'
export default axios.create({
    baseURL: 'http://localhost:3000',
})
docker compose run --rm backend bundle exec rails c
pry> 20.times do
  Book.create(
    title: Faker::Book.title,
    author: Faker::Book.author,
    publisher: Faker::Book.publisher,
    genre: Faker::Book.genre
  )
end

データの確認
pry> Book.all

を行いました。

環境は
https://qiita.com/gabakugik/items/cded7e8aaa014025d8ba
で作りました。

0

4Answer

ルーティングに問題有りです。

Rails.application.routes.draw do
  namespace :api do
    resources :books, only: [:show] # これだとshowしか使えない
  end
end

Rails.application.routes.draw do
  namespace :api do
    resources :books # CRUD使えるのでこれでおk
  end
end

http://localhost:3000/api/books
を行うと
No route matches [GET] "/api/books"
になります

これが発生しているのは only: [:show] していて、showアクション以外が使えない状態になっているためです。
[GET] /api/booksを許可したいのであればindexアクションも許可してください。
ルーティングはRailsガイドが一番わかりやすいです。
https://railsguides.jp/routing.html#crud%E3%80%81verb%E3%80%81%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3

というか基本的なCRUDのみしか使用しないのであれば一旦scaffoldで作成してしまうのが楽ですよ。
scaffoldでどんなものが生成されるか理解したあと、自前で作ってみることをおすすめします。

これが参考になりそうです
https://qiita.com/kumagaias/items/e2b427124e1efd0eb122#controller

1Like

Comments

  1. あと追加で、seedで実行できなければrails consoleで実行してみてください。

    docker compose run --rm backend bundle exec rails c
    pry> 20.times do
      Book.create(
        title: Faker::Book.title,
        author: Faker::Book.author,
        publisher: Faker::Book.publisher,
        genre: Faker::Book.genre
      )
    end
    
    # データが入ったか確認
    pry> Book.all
    

    他の方もコメントされてますが、db:seedがうまくいってるかどうかが見てる側はわからないので、seed実行時のログを出してもらったほうがアドバイスしやすいです。

  2. あと、ルーティングのことしか指摘してないですが、controller側も処理を書かないといけないので注意してください。

  3. @gabakugik

    Questioner

    修正しました
    localhost:3000のほうではJson表示されました。
    ただlocalhost:5173のほうでは表示されないです。
    データも21個入りました。
    ありがとうございます。

  4. @gabakugik
    jbuilderを使用されているようですが、jbuilder templateは用意されてますか?(上に書いてなかったので)
    jbuilderあまり使用してないのでview templateを用意していない場合の挙動はわからないんですが、必要なんじゃないですかね。
    今回であれば、app/views/api/books/index.json.jbuilderを用意するとかでしょうか。
    https://github.com/rails/jbuilder

  5. @gabakugik

    Questioner

    つくってないです。

  6. localhost:3000のほうではJson表示されました。

    あ、json表示されてるんですね。フロント側に問題がありそう。
    リクエスト時のRails側のログは貼ってます?(貼ってあるのvue側のログっぽいので)

  7. @gabakugik

    Questioner

    今作りました。

  8. @gabakugik

    Questioner

    railsのログはどこで見られます?

  9. railsの起動コマンドが貼られてないのでわからないんですが、docker compose up -dでやってたらログは表示されないですね、バックグラウンドで起動するので。
    docker compose upで起動するとログが表示されるはずなので、vueからのリクエスト時に何が出ているか貼ってもらえるとアドバイスできます。

    あと、ログに関して貼っていただいてますが、なんのログかわからないので一言添えてもらえるとわかりやすいかなと思います。

  10. 環境構築の記事見て理解しました。
    フロントをdocker環境で行っているんですね。しかもバックエンドと別々のcompose.ymlで。
    …なぜかフロントのcompose.ymlにDBがあるのも気になります。

    開発環境においてnpmのプロジェクトをdockerに乗せるのは微妙(無駄に遅い)なので、フロントの環境構築自体は素のnpmでやったほうがいいです。超かんたんですし。
    バックエンド:dockerで構築
    フロントエンド:素のnpmで構築
    の構成で再度やり直すことをおすすめします。

    フロントをdockerのままで進めるとdocker networkの話までかんできます。
    そもそもそこは本質ではないと思うので、さっさとフロントからdockerを切り捨てて進めるほうが懸命だと思います。

  11. @gabakugik

    Questioner

    自分もこんなにできないもんなんだなって思いました。
    まさか表示されないとは

App.vue
import axios from 'axios';

App.vue
import axios from './lib/axios';

にしてみてはどうでしょうか?
('./lib/axiosの部分は記述からは正しいパスがわからないため、適時書き換えてください。)

1Like

Comments

  1. @gabakugik

    Questioner

    import axios from './lib/axios';
    したらエラーはなくなりましたが、表示もされないです

  2. App.vue
    const fetchBooks = () => {
      axios.get('/api/books').then(
        (res) => {
          books.value = res.data.books;
        },
        (error) => {
          console.log(error);
        }
      );
    };
    

    その状態で、上記のコードのres.dataの中身をconsole.logで表示してみてみると何が表示されますか?

  3. @gabakugik

    Questioner

    何も表示されないです。

  4. ブラウザでhttp://localhost:3000/api/booksを開いたときに想定したjsonは表示されますか?
    されないならapp/controllers/api/books_controller.rbindexメソッドを以下のように修正してみていただきたいです

    app/controllers/api/books_controller.rb
    def index
        @book = Book.all
        render json: { books: @book }
    end
    
  5. @gabakugik

    Questioner

    app/controllers/api/books_controller.rb
    def index
        @book = Book.all
        render json: { books: @book }
    end
    

    でlocalhost:3000で表示されます

  6. ありがとうございます。
    他に考えられることで言いますと、RailsとVueがそれぞれ別のコンテナイメージで起動している場合はRails, Vue間のlocalhostへのアクセスは失敗するのですが、それぞれ別々のDockerfileから起動してたりしますか、、?

  7. @gabakugik

    Questioner

    別別のdockerfileから起動してます。
    環境はqiitaの最後に書いてます。

  8. なるほどです。
    下記の記事を参考にdocker-compose.yamlにネットワークの設定を追加して、axios.createlocalhostのところをdocker-compose.yamlで定義したrailsコンテナのサービス名に書き換えてみてください
    https://qiita.com/kivianko/items/7f0c718157548ae22cdf

  9. @gabakugik

    Questioner

    すいません。ちょっとわからないのですが
    networks:
    - external-api
    networks:
    external-api:
    external: true
    などを追加記載して
    docker network create external-api作ればいいのでしょうか?

  10. やること一つ目はそれで合ってます!そんな感じにexternalなnetworkを作成してください。
    そしたらaxios.createの部分を次のように書き換えてください(「backend」の部分はdocker-compose.yamlに記載したrailsコンテナのサービス名で、GABAKU GIKさんの参照元の記事のものをそのまま入力しています。)

    frontend/vite-project/src/lib/axios.ts
    import axios from 'axios'
    export default axios.create({
        baseURL: 'http://backend:3000',
    })
    
  11. @gabakugik

    Questioner

    わかりました。やってみます。

  12. @gabakugik

    Questioner

    version: '3'
     srvices:
      db:
        image: mysql:8.0
        environment:
          MYSQL_ROOT_PASSWORD: password
        ports:
          - '3306:3306'
        command: --default-authentication-plugin=mysql_native_password
        volumes:
          - mysql-data:/var/lib/mysql
         networks:
           - external-api
      frontend:
        build:
          context: ./frontend
          dockerfile: Dockerfile
            #  args
            #  WORKDIR: app
        volumes:
          - ./frontend/vite-project:/app/vite-project
        ports:
          - '5173:5173'
        depends_on:
          - backend
        stdin_open: true
        tty: true
        command: yarn dev --host
        networks:
          - external-api
      backend:
        build:
          context: ./backend
          dockerfile: Dockerfile
          args:  #=== Dockerfileに引数として渡せる
            WORKDIR: app
        command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
        volumes:
          - './backend:/app'
        ports:
          - "3000:3000"
        environment:
            RAILS_ENV: development
        depends_on:
           - db
        stdin_open: true
        tty: true
    volumes:
      mysql-data:
        driver: local
        networks:
          - external-api
          
     networks:
        external-api:
        external: true
    
    

    という形で作ってみたのですが
    docker-compose buildをすると

    ERROR: yaml.parser.ParserError: while parsing a block mapping
      in "./docker-compose.yml", line 1, column 1
    expected <block end>, but found '<block mapping start>'
      in "./docker-compose.yml", line 2, column 2
    

    が出ます。
    やり方間違えています?

    docker network create external-api
    

    はできました。

  13. ざっくりみた感じ下記が間違っていそうです

    • servicessrvicesになっている
    • 2行目から行の初めに謎の空白がある
    • networkのインデントがずれている。正しいのは下記
    networks:
      external-api:
        external: true
    
  14. Dockerfileは別々ですが、docker-compose.ymlはbackendとfrontendで別れていないのでnetworksの設定は気にしなくていいはずです。
    仮に別れていたとしてもポートバインディングさえできていればfrontendからbackendへのAPIリクエストは問題ない気がするけど、どうなんでしょう。

    rubyもrailsも触ったことなくて何が悪いか分からなかったので、質問主様のQiita記事を参考に1時間くらいで環境構築してみました。
    db:seedでデータ登録もできましたし、http://localhost:3000/api/booksでデータを受け取って表示することもできました。
    image.png

    設定ミスや単純なプログラミングミス、デバッグ不足、どこかしらのコピペを貼り付けてる印象がかなり強かったです(特にデバッグ不足)。
    仕事中のため何が悪いかすぐには書けないですが、解消できていないようであれば時間とってコメントに書こうと思います。

設定ミスや単純なプログラミングミス、デバッグ不足、どこかしらのコピペを貼り付けてる印象がかなり強かったです(特にデバッグ不足)。
仕事中のため何が悪いかすぐには書けないですが、解消できていないようであれば時間とってコメントに書こうと思います。

このコメントをした者です。
corsの設定が誤っていてフロントからのAPIリクエストがブロックされているっぽいです。
corsの問題を解消したらとりあえずフロントエンドにデータが返ってくるようになるとおもいます。
db:seedについても書きました。

cors

originsはorigins "http://localhost:5173"ではなくorigins "localhost:5173"
(むしろorigins "*"で良い)


(backend/config/initializersの下にcors.rbがある前提です、Qiita記事からはどのディレクトリに置いてあるか読み取れませんでした)

db:seed

記事を見たところbackend/configの下にseeds.rbを置いているが、backend/dbの下に置くのが正しそう。

1Like

Comments

  1. @gabakugik

    Questioner

    すいません。corsのところのorigins "*"にしたんですが、表示されませんでした。
    backend/config/initializersのところにあります
    localhost:5173で表示されたんですよね?

  2. 他の回答でも同様なのですが、表示されません、分かりません だけだとコチラもアドバイスしづらいです。
    タイピングミスしてたりする可能性もあるので、どういう状況なのか詳細を記載したり、http://localhost:3000/api/books にブラウザでアクセスした際のスクショやDockerコンテナのログ、Networkのレスポンスなど回答をする上で役に立ちそうな情報を書いていただけると助かります。

    回答者がどんな情報を欲しているか考えて返信することを心掛けてみてください。

  3. @gabakugik

    Questioner

    申し訳ありませんでした
    http://localhost:3000/api/books ではデータが表示されています。
    localhost:5173ではFailed to load resource: the server responded with a status of 404 (Not Found)が表示されています。

  4. @gabakugik

    Questioner

    render 'all', formats: 'json', handlers: 'jbuilder'だと表示されません。
    すいませんがcontrollerってどう書いてます?
    render status: 200, json: {book: @book}だと表示されます。

  5. すいません。corsのところのorigins "*"にしたんですが、表示されませんでした。
    backend/config/initializersのところにあります
    localhost:5173で表示されたんですよね?

    corsはこう書いてます。

    backend/config/initializers/cors.rb
    Rails.application.config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins "localhost:5173"
        resource "*",
          headers: :any,
          methods: [:get, :post, :put, :patch, :delete, :options, :head]
      end
    end
    

    申し訳ありませんでした
    http://localhost:3000/api/books ではデータが表示されています。

    JSONが表示されているとは思うのですが、念の為スクショいただけますでしょうか。
    回答者からはどんなデータが表示されているのか分かりません。

    localhost:5173ではFailed to load resource: the server responded with a status of 404 (Not Found)が表示されています。

    こちらもそもそもAPIリクエストのURLが間違っている可能性があり、回答者は確認ができません。
    404が発生しているリクエストがブラウザのネットワークタブで確認できますのでスクショ または http://localhost:3000/api/books宛にちゃんとリクエストが飛んでいるのか確認をお願いします。
    (ネットワークタブの出し方はググれば出てくるとおもいます)

    render 'all', formats: 'json', handlers: 'jbuilder'だと表示されません。

    はい、コピペしたので自分も最初はその書き方だったのですが、JSONデータがレスポンスされなかったため書き直しました。

    class Api::BooksController < ApplicationController
      def index
        books = Book.all
        # フロント側はbooks.value = res.dataで受け取る
        render json: books
    
        # 質問主さんの書き方の場合、フロント側はbooks.value = res.data.bookで受け取る
        # render json: {book: books}
      end  
    
      def show
        book = Book.find(params[:id])
        render json: book
      end
    end
    
    
  6. @gabakugik

    Questioner

    コアも修正しました。
    BooksControllerも修正しました。スクリーンショット 2023-09-06 195140.png
    localhost:3000/api/booksで表示されるようになりました

  7. @gabakugik

    Questioner

    ネットワークタブ404のスクショを送ります。
    多分言っている意味はこれだと思うのですが
    スクリーンショット 2023-09-06 212853.png

  8. ありがとうございます!
    URLがlocalhost:5173になっちゃってますね。
    backend( rails )が使用しているポートは3000なので、localhost:3000が正しいです。

    compose.yaml
      ...
      backend:
        ...
        ports:
          - "3000:3000"
      ...
    

    axiosのコードを修正してください。

    frontend/vite-project/src/lib/axios.ts
    import axios from 'axios'
    
    export default axios.create({
        baseURL: 'http://localhost:3000',
    })
    
    
  9. @gabakugik

    Questioner

    frontend/vite-project/src/lib/axios.ts
    直したんですがスクリーンショット 2023-09-06 232802.png
    後どこを見ればいいでしょうか?
    すいません。

  10. lib直下でaxiosのexport宣言を行っているので、App.vueで使用するaxiosもlibから読み込んでください。

    frontend/vite-project/src/App.vue
    <script setup lang="ts">
    ...
    import axios from './lib/axios';
    ...
    ..
    .
    <style scoped></style>
    
  11. @gabakugik

    Questioner

    ほかのも全部200OKになりました。
    スクリーンショット 2023-09-06 235657.png

  12. @gabakugik

    Questioner

    その後こうなりました。スクリーンショット 2023-09-07 000531.png

  13. スクショ貼っていただいたのはいいのですが、どこを見て欲しいのか、何を伝えたいのかが分からないです。

    APIリクエストが200になったのであればフロント側でデータ表示されるようにできるはずです。

  14. @gabakugik

    Questioner

    申し訳ないのですが表示されてないです。
    自分もどうしたらいいかわからないんです。
    他力本願ですいません。
    dockerでログをはくようになりました
    Processing by Api::BooksController#index as HTML
    backend_1 | Book Load (1.0ms) SELECT books.* FROM books
    backend_1 | ↳ app/controllers/api/books_controller.rb:5:in `index'
    backend_1 | Completed 200 OK in 14ms (Views: 5.9ms | ActiveRecord: 3.3ms | Allocations: 12036)
    そのできた経緯をqiitaの記事にしていただくことはできないでしょうか?

  15. 多分、僕のControllerのコードをコピペして自分のController書き換えちゃってますよね。

    JSONの形が変わってしまうので、フロント側のコードの修正も必要だと思います。

    フロント側でレスポンスデータ(JSON) は受け取れていて、そのデータの中身はネットワークタグやconsole.logで確認可能です。
    教えちゃうとコピペするだけで質問者様の力にならないので、ここを頑張ってデバッグして解決してみてください。

Rubyは経験ないですが、db:seedでbooksテーブルにレコード登録してるんですね。

booksテーブルにちゃんとレコードが登録されてあるかCUIまたはDBクライアントツールで確認してみてはどうでしょうか。

0Like

Comments

  1. @gabakugik

    Questioner

    CUIで確認したんですけどちゃんとデータがはいってないみたいなんです。

  2. であればDBの接続情報が誤っているのではないでしょうか。
    もしくは、実はdb:seedの実行が失敗している。

    CUIでbooksにレコードを登録したあとにBook.allしたらデータが表示されるのか確認してみてはどうでしょうか。

  3. @gabakugik

    Questioner

    具体的にどうすればいいんでしょうか。
    すいません。教えてください。

  4. うーん。

    CUIでbooksテーブルに直接INSERT実行→rails cのBook.allでデータ取得できたらdb:seedでのデータ登録がうまく行ってないってことが分かるのでは無いでしょうか。

    railsは全く分からないのでこれ以上はすみません。

Your answer might help someone💌