This is my first time touching Nuxt.js. I thought I'd make a Todo app, but I thought I'd hit the API, so I prepared the server side as well.
The server side is Ruby on Rails (API), the client side is Nuxt.ts (Nuxt.js + TypeScript), and the DB is postgres.
Regarding the environment construction, both server side/client side are running on Docker, and the directory structure is summarized in monolithic.
↓ Click here for the source code
macOS Catalina : version 10.15.4 It is assumed that Docker for mac is already installed.
Directory structure
.
├── client-side
├── server-side
└── docker-compose.yml
Create dockerfile under server-side /
.
Dockerfile
FROM ruby:2.7.0
RUN apt-get update -qq && \
apt-get install -y \
build-essential \
libpq-dev \
nodejs \
postgresql-client
WORKDIR /app
COPY Gemfil Gemfile.lock /app/
RUN bundle install
Similarly, create Gemfile and Gemfile.lock under server-side /
.
Describe the following in the Gemfile.
Gemfile
source 'https://rubygems.org'
gem 'rails', '6.0.3'
You can leave Gemfile.lock empty.
I will write the rails and postgres settings in docker-compose.yml.
docker-compose.yml
version: '3.8'
volumes:
db_data:
services:
db:
image: postgres
volumes:
- db_data/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
server-side:
build: ./server-side/
command: bundle exec rails server -b 0.0.0.0
image: server-side
ports:
- 3000:3000
volumes:
- ./server-side:/server-app
tty: true
stdin_open: true
depends_on:
- db
links:
- db
rails new
in API modeIf you hit the following command, rails related files will be created under server-side /
.
$ docker-compose run server-side rails new . --api --force --database=postgresql --skip-bundle
database.yml
If this is left as it is, the DB container cannot be accessed from the server-side container, so modify the contents of database.yml
.
I think it looks like this
database.yml
default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
Edit as follows.
database.yml
default: &default
adapter: postgresql
encoding: unicode
host: db
user: postgres
password: password
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
server-side
hostWith this setting, you can access server-side from Nuxt.
server-side/config/environments/development.rb
config.hosts << "server-side"
Create a db by hitting the following command.
$ docker-compose run server-side rails db:create
Access localhost: 3000
by typing the following command.
If the rails default screen is displayed, it's OK!
$ docker-compose up -d
Hit the following command to enter the container and proceed with the work.
$ docker exec -it server-side bash
Set the routing.
routes.rb
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
namespace :api do
namespace :v1 do
resources :todos do
collection do
get :complete
end
end
end
end
end
Create Todo model, todos controller.
$ rails g model Todo title:string isDone:boolean
$ rails db:migrate
$ rails g controller api::v1::todos
The contents of the controller are written as follows.
api/app/controllers/api/v1/posts_controller.rb
class Api::V1::TodosController < ApplicationController
before_action :set_todo, only: [:update, :destroy]
def index
todos = Todo.where(isDone: false)
render json: { status: 'SUCCESS', message: 'Loaded todos', data: todos }
end
def complete
todos = Todo.where(isDone: true)
render json: { status: 'SUCCESS', message: 'Loaded todos', data: todos }
end
def create
todo = Todo.new(todo_params)
if todo.save
render json: { status: 'SUCCESS', data: todo }
else
render json: { status: 'ERROR', data: todo.errors }
end
end
def destroy
@todo.destroy
render json: { status: 'SUCCESS', message: 'Deleted the todo', data: @todo }
end
def update
if @todo.update(todo_params)
render json: { status: 'SUCCESS', message: 'Updated the todo', data: @todo }
else
render json: { status: 'ERROR', message: 'Not updated', data: @todo.errors }
end
end
private
def set_todo
@todo = Todo.find(params[:id])
end
def todo_params
params.require(:todo).permit(:title, :isDone)
end
end
Refer to This article to check if CRUD operations can be performed using Postman. You can also check it with the curl command, but Postman is probably easier.
Basically, just follow the Official Installation. It is assumed that node is already installed. (In this environment, LTS ver. 14.15.1 as of 12/15 is used.)
First, let's create a template with create-nuxt-app
.
$ npx create-nuxt-app client-side
I think you will be asked various questions, but this time I set it as follows. (Others are default)
terminal
? Project name: client-side
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: None
? Nuxt.js modules: Axios
? Linting tools: None
? Testing framework: None
? Rendering mode: Single Page App
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: None
Please set the settings around here to your liking. All options can be found here (https://github.com/nuxt/create-nuxt-app/blob/master/README.md).
Create a Dockerfile under client-side /
.
Dockerfile
FROM node:14.15.1
WORKDIR /client-app
COPY package.json yarn.lock ./
RUN yarn install
CMD ["yarn", "dev"]
client-side
setting to docker-compose.ymlAdd the client-side
setting to docker-compose.yml that describes the server-side
setting.
docker-compose.yml
version: '3.8'
volumes:
db_data:
services:
db:
image: postgres
volumes:
- db_data/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
server-side:
build: ./server-side/
image: server-side
ports:
- 3000:3000
volumes:
- ./server-side:/server-app
command: bundle exec rails server -b 0.0.0.0
tty: true
stdin_open: true
depends_on:
- db
links:
- db
#Add below from here
client-side:
build: ./client-side/
image: client-side
ports:
- 8000:8000
volumes:
- ./client-side:/client-app
- /client-app/node_modules
command: sh -c "yarn && yarn dev"
If this is left as it is, an error will occur, so set port and host as follows.
nuxt.config.js
export default {
// Disable server-side rendering (https://go.nuxtjs.dev/ssr-mode)
ssr: false,
//Add here
server: {
port: 8000,
host: '0.0.0.0',
},
//Omitted below
}
Type the following command to access localhost: 8000
and the Nuxt.js default screen will be displayed.
$ docker-compose up -d
This completes the environment construction!
Finally, we will hit the server-side API from the client side. Impressive moment. .. ..
For CORS, this article will be helpful.
There was a solution in Official and README on GitHub.
Install @ nuxtjs/proxy
referring to the description in the README, and edit app/nuxt.config.js
as follows.
Since the server side port number was specified as 3000, here is server-side: 3000
. (Communication between containers is resolved by the container name, so it is set to server-side
instead of localhost
.)
$ yarn add @nuxtjs/proxy
app/nuxt.config.js
modules: [
'@nuxtjs/axios',
'@nuxtjs/proxy'
],
//Add the following
proxy: {
'/api': {
target: 'http://server-side:3000',
pathRewrite: {
'^/api': '/api/v1/',
},
},
},
I set it because I want to use it around here, but CRUD operation is possible without it.
shell
$ yarn add @nuxtjs/composition-api
client-side/nuxt.config.js
modules: [
'@nuxtjs/proxy',
//add to
'@nuxtjs/axios',
'@nuxtjs/composition-api',
],
client-side/tsconfig.json
"types": [
"@types/node",
"@nuxt/types",
#add to
"@nuxtjs/axios"
]
Create a new models/todo.ts
directory in client-side
and write the following.
todo.ts
export interface ITodo {
id: number;
title: string;
isDone: boolean;
}
Actually, it should be divided into components and written, but this time I thought it would be easier to see if it was put together in one file, so I will put it together.
Describe the following contents in client-side/pages/index.vue
.
client-side/pages/index.vue
<script lang="ts">
import {
defineComponent,
reactive,
ref,
onMounted,
} from "@nuxtjs/composition-api";
import { ITodo } from "../models/todo";
import $axios from "@nuxtjs/axios";
export default defineComponent({
setup(_, { root }) {
onMounted(() => {
getTodo();
});
const todoItem = reactive({
title: "",
isDone: false,
});
const todoList = ref<ITodo[]>([]);
const completeTodoList = ref<ITodo[]>([]);
//post to do
const addTodo = async () => {
try {
await root.$axios.post("/api/todos/", {
title: todoItem.title,
isDone: todoItem.isDone,
});
getTodo();
todoItem.title = "";
} catch (e) {
console.log(e);
}
};
//get todo
const getTodo = async () => {
try {
const response = await root.$axios.get("/api/todos");
todoList.value = { ...response.data.data };
getCompleteTodo();
} catch (e) {
console.log(e);
}
};
//update todo
const updateTodo = async (i: number, todo: ITodo) => {
try {
const newTodo = todoList.value[i].title;
await root.$axios.patch(`/api/todos/${todo.id}`, { title: newTodo });
} catch (e) {
console.log(e);
}
};
//delete todo
const deleteTodo = async (id: number) => {
try {
await root.$axios.delete(`/api/todos/${id}`);
getTodo();
} catch (e) {
console.log(e);
}
};
//to do done
const completeTodo = async (todo: ITodo) => {
try {
todo.isDone = !todo.isDone;
await root.$axios.patch(`/api/todos/${todo.id}`, {
isDone: todo.isDone,
});
getTodo();
} catch (e) {
console.log(e);
}
};
// complete_get todo
const getCompleteTodo = async () => {
try {
const response = await root.$axios.get("/api/todos/complete");
completeTodoList.value = { ...response.data.data };
} catch (e) {
console.log(e);
}
};
return {
todoItem,
todoList,
completeTodoList,
addTodo,
deleteTodo,
updateTodo,
completeTodo,
};
},
});
</script>
<template>
<div class="container">
<section class="todo-new">
<h1>Add todos</h1>
<input v-model="todoItem.title" type="text" placeholder="Fill in todo" />
<button @click="addTodo()">Add Todo</button>
</section>
<section class="todo-index">
<h1>Incomplete todos</h1>
<ul>
<li v-for="(todo, i) in todoList" :key="i">
<input
class="item"
type="checkbox"
:checked="todo.isDone"
@change="completeTodo(todo)"
/>
<input
class="item"
type="text"
v-model="todo.title"
@change="updateTodo(i, todo)"
/>
<button @click="deleteTodo(todo.id)">delete</button>
</li>
</ul>
</section>
<section class="todo-complete">
<h1>Complete todos</h1>
<ul>
<li v-for="(todo, i) in completeTodoList" :key="i">
<input
class="item"
type="checkbox"
:checked="todo.isDone"
@change="completeTodo(todo)"
/>
{{ todo.title }}
<button @click="deleteTodo(todo.id)">delete</button>
</li>
</ul>
</section>
</div>
</template>
<style>
.container {
margin: 80px auto;
min-height: 100vh;
text-align: center;
}
section {
margin-bottom: 30px;
}
.item {
font-size: 1rem;
margin: 0 10x;
}
li {
list-style: none;
margin-bottom: 0.5em;
}
</style>
If you do docker-compose up and access localhost: 8000
, you will see the following screen.
Try adding/editing/deleting todo.
It was my first time to write a Dockerfile from scratch, so it was a good study. I have nothing to know about Nuxt.js, so I will study it. If you have any questions such as "The code here should be more like this!", Please give me some advice.
Recommended Posts