環境構築はこちらから
https://qiita.com/gabakugik/items/53dc8888f7085d15f4d8
今回Next.js(React)×Ruby on Railsを使いデータを表示させようと思います。
ツリー構造
|-- backend
| |-- Dockerfile
| |-- Gemfile
| |-- Gemfile.lock
| |-- README.md
| |-- Rakefile
| |-- app
| | |-- channels
| | |-- controllers
| | |-- jobs
| | |-- mailers
| | |-- models
| | `-- views
| |-- bin
| | |-- bundle
| | |-- rails
| | |-- rake
| | `-- setup
| |-- config
| | |-- application.rb
| | |-- boot.rb
| | |-- cable.yml
| | |-- credentials.yml.enc
| | |-- database.yml
| | |-- environment.rb
| | |-- environments
| | |-- initializers
| | |-- locales
| | |-- master.key
| | |-- puma.rb
| | |-- routes.rb
| | `-- storage.yml
| |-- config.ru
| |-- db
| | |-- migrate
| | |-- schema.rb
| | `-- seeds.rb
| |-- lib
| | `-- tasks
| |-- log
| | `-- development.log
| |-- public
| | `-- robots.txt
| |-- storage
| |-- test
| | |-- channels
| | |-- controllers
| | |-- fixtures
| | |-- integration
| | |-- mailers
| | |-- models
| | `-- test_helper.rb
| |-- tmp
| | |-- cache
| | |-- development_secret.txt
| | |-- pids
| | |-- restart.txt
| | |-- sockets
| | `-- storage
| `-- vendor
|-- docker-compose.yml
`-- frontend
|-- Dockerfile
`-- app
|-- README.md
|-- app
|-- components
|-- next-env.d.ts
|-- next.config.mjs
|-- node_modules
|-- package-lock.json
|-- package.json
|-- postcss.config.mjs
|-- public
|-- tailwind.config.ts
|-- tsconfig.json
|-- types
`-- yarn.lock
まずはbackendでデータを作ります。
docker compose run backend bundle exec rails generate model Todo title:string content:text
docker compose run backend bundle install
docker compose run backend bundle exec rails db:migrate
rack-corsのインストール
gem 'rack-cors'のコメントアウトをはずす。
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'localhost:3001' # Next.jsを動作させているアドレスとポート番号
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
docker compose run backend bundle install
docker compose build
docker compose run backend bundle exec rails generate controller Todos
app/controllers/todos_controller.rb
class TodosController < ApplicationController
# GET /todos
def index
# 日付が新しい順に10件まで取得する
@todos = Todo.all.order(created_at: :desc).limit(10)
render json: @todos
end
end
config/routes.rb
Rails.application.routes.draw do
resources :todos, except: [:new, :edit]
end
db/seeds.rb
Todo.create(title: 'Todo 1', content: 'Todo 1 の内容')
Todo.create(title: 'Todo 2', content: 'Todo 2 の内容')
Todo.create(title: 'Todo 3', content: 'Todo 3 の内容')
docker compose run backend bundle exec rails db:seed
docker-compose up -d
http://localhost:3000/todos
でデータが表示されるか確認
次はフロント部分を作っていきます
docker compose run --rm frontend npm install axios
docker compose run --rm frontend npm install swr
styles/globals.cssをすべて削除。
frontend/app/app/page.tsx
// トップページ
export default function Home() {
return (
<div className="flex flex-col justify-center items-center">
トップページ
</div>
);
}
docker-compose up -d
でトップページと表示されればOK
frontend/types/Todo.ts
export type TodoType = {
id: number;
title: string;
content: string;
};
frontend/components/Todo.tsx
import { TodoType } from '../types/Todo';
// Todo一つを表示するコンポーネント
const Todo = ({ todo }: { todo: TodoType }) => {
return (
<div className="focus:outline-none mb-7 bg-white p-6 shadow rounded">
<div className="flex items-center border-b border-gray-200 pb-6">
<div className="flex items-start justify-between w-full">
<div className="pl-3">
<p className="focus:outline-none text-lg font-medium leading-5 text-gray-800">
{todo.title}
</p>
</div>
</div>
</div>
<div className="px-2">
<p className="focus:outline-none text-sm leading-5 py-4 text-gray-600">{todo.content}</p>
</div>
</div>
);
};
export default Todo;
frontend/components/Todos.tsx
"use client";
import useSWR from 'swr';
import { TodoType } from '../types/Todo';
import Todo from './Todo';
import Link from 'next/link';
// Fetcher function to get data from the API
const fetcher = (url: string) => fetch(url).then(res => res.json());
const Todos = () => {
// Use SWR to fetch and cache the Todo list
const { data: todos, error } = useSWR<TodoType[]>('http://localhost:3000/todos', fetcher);
if (error) return <div>Failed to load</div>;
if (!todos) return <div>Loading...</div>;
return (
<div className="space-y-6 w-3/4 max-w-lg pt-10">
<label className="block text-xl font-bold text-gray-700">Todo Index</label>
<div className="items-center justify-center">
{todos.map((todo) => (
<Link
href={`todos/${todo.id}`}
key={todo.id}
>
<Todo todo={todo} />
</Link>
))}
</div>
</div>
);
};
export default Todos;
frontend/app/app/page.tsx
import Todos from '@/components/Todos';
// トップページ
export default function Home() {
return (
<div className="flex flex-col justify-center items-center">
<Todos />
</div>
);
}
これで3件のデータが表示されます。