개발 방법
Vue는 자바스크립트와 Node.JS를 사용하므로 이를 미리 설치해야 한다. 이후, 개발 순서는 다음과 같다.
- Javascript Node.JS 설치
- MongoDB 이해: MongoDB introduction 참고
- MongoDB 설치: Install MongoDB 참고
- 예제 개발: Example - Simple MEVN 참고
Simple MEVN 예제는 To do list를 CRUD(create, read, update, delete)하는 웹 어플리케이션이다. 구조는 크게 두 부분으로 나뉘어져 있고, 다음과 같다.
- CRUD API + Express + Mongo DB server
- App Form UI + Vue server
우선 CRUD API + MongoDB 서버부터 개발해 보자.
CRUD API 서버 개발
다음처럼 폴더와 파일을 만든다.
mkdir vurcrudappapi
cd vurcrudappapi
touch server.js
다음과 같이 API 기능을 제공할 폴더를 만든다.
mkdir api
mkdir api/controllers
mkdir api/models
mkdir api/routes
다음과 같이 파일을 만든다.
touch api/controllers/taskController.js
touch api/model/taskModel.js
touch api/routes/taskRoutes.js
다음과 같이 package.json 파일을 만든다.
npm init
그리고, 다음 패키지를 설치한다.
npm i express cors body-parser mongoose
npm i nodemon --save-dev
package.json 안의 scripts 부분을 다음처럼 수정한다.
"scripts": {
"start": "nodemon server.js"
},
taskController.js 파일을 다음과 같이 코딩한다.
const mongoose = require('mongoose');
const task = mongoose.model('task');
exports.list_all_tasks = (req, res) => {
task.find({}, (err, tasks) => {
if (err) res.send(err);
res.json(tasks);
});
};
exports.create_a_task = (req, res) => {
const newTask = new task(req.body);
newTask.save((err, task) => {
if (err) res.send(err);
res.json(task);
});
};
exports.read_a_task = (req, res) => {
task.findById(req.params.taskId, (err, task) => {
if (err) res.send(err);
res.json(task);
});
};
exports.update_a_task = (req, res) => {
task.findOneAndUpdate(
{ _id: req.params.taskId },
req.body,
{ new: true },
(err, task) => {
if (err) res.send(err);
res.json(task);
}
);
};
exports.delete_a_task = (req, res) => {
task.deleteOne({ _id: req.params.taskId }, err => {
if (err) res.send(err);
res.json({
message: 'task successfully deleted',
_id: req.params.taskId
});
});
};
taskModel.js 파일은 다음과 같이 코딩한다.
const mongoose = require('mongoose');
const { Schema } = mongoose;
const taskSchema = new Schema(
{
task1: {
type: String,
required: 'task1 cannot be blank'
},
task2: {
type: String,
required: 'task2 cannot be blank'
}
},
{ collection: 'task' }
);
module.exports = mongoose.model('task', taskSchema);
RoutesModel.js 파일을 다음과 같이 수정한다.
const taskBuilder = require('../controllers/taskController');
module.exports = app => {
app
.route('/tasks')
.get(taskBuilder.list_all_tasks)
.post(taskBuilder.create_a_task);
app
.route('/tasks/:taskId')
.get(taskBuilder.read_a_task)
.put(taskBuilder.update_a_task)
.delete(taskBuilder.delete_a_task);
};
다음과 같이 server.js 파일을 코딩한다.
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
global.Task = require('./api/models/taskModel');
const routes = require('./api/routes/taskRoutes');
mongoose.Promise = global.Promise;
mongoose.set('useFindAndModify', false);
mongoose.connect(
'mongodb://localhost/Vuecrudapp',
{ useNewUrlParser: true }
);
const port = process.env.PORT || 3000;
const app = express();
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
routes(app);
app.listen(port);
app.use((req, res) => {
res.status(404).send({ url: `${req.originalUrl} not found` });
});
console.log(`Server started on port ${port}`);
위의 mongoose.connect 함수는 Vuecrudapp 데이터베이스를 연결하는 역할을 한다. mongo Compass를 사용해 Vuecrudapp 데이터베이스가 생성된 후, Express app을 통해 bodyParser와 cors 미들웨어를 사용함을 설정한다.
이제 taskRoutes.js 에 정의된 코드를 통해 라우팅을 하고, 3000 번 포트를 통해 connection을 기다린다. 이제 Mongo DB를 실행한 후, 다음과 같이 npm을 통해 실행한다.
npm run start
생성된 DB는 Mongo compass 프로그램을 사용해 내용을 확인할 수 있다. 아울러, Postman을 이용해 API를 확인할 수 있다.
APP 서버 개발
다음과 같이 Vue CLI 환경을 설치한다.
npm install -g @vue/cli
그리고, vuecrudapp 프로젝트를 다음처럼 생성한다.
vue create vuecrudapp
다음과 같이 실행하면, http://localhost:8080/ 에서 기본 Vue 인터페이스를 확인할 수 있을 것이다.
cd vuecrudapp
npm run serve
서버를 종료하고, 다음과 같이 폴더와 파일을 만든다.
cd src
touch components/TaskTest.vue
touch components/Taskform.vue
mkdir views
touch views/Edit.vue
touch views/New.vue
touch views/Show.vue
touch views/Test.vue
touch views/Tasks.vue
mkdir helpers
touch helpers/Helpers.js
touch Router.js
패키지를 설치한다.
npm i axios semantic-ui-css vue-flash-message
router.js 를 코딩한다.
import Vue from 'vue';
import Router from 'vue-router';
import Tasks from './views/Tasks.vue';
import New from './views/New.vue';
import Show from './views/Show.vue';
import Edit from './views/Edit.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
linkActiveClass: 'active',
routes: [
{
path: '/',
redirect: '/tasks'
},
{
path: '/tasks',
name: 'tasks',
component: Tasks
},
{
path: '/tasks/new',
name: 'new-task',
component: New
},
{
path: '/tasks/:id',
name: 'show',
component: Show
},
{
path: '/tasks/:id/edit',
name: 'edit',
component: Edit
}
]
});
등록된 라우트의 의미는 다음과 같다.
/tasks - Mongo DB 데이터베이스의 모든 task를 표시한다.
/tasks/new - 새로운 task를 생성한다.
/tasks/:id - id에 해당하는 task를 표시한다.
/tasks/:id/edit - id에 해당하는 task를 수정한다.
다음과 같이 main.js 를 수정한다.
import Vue from 'vue'
import App from './App.vue'
import 'semantic-ui-css/semantic.css';
import router from './router'
new Vue({
router,
render: h => h(App),
}).$mount('#app')
app.vue를 수정한다.
<template>
<div id="app">
<div class="ui inverted segment navbar">
<div class="ui center aligned container">
<div class="ui large secondary inverted pointing menu compact">
<router-link to="/tasks" exact class="item">
<i class="tasks icon"></i> Tasks
</router-link>
<router-link to="/tasks/new" class="item">
<i class="plus circle icon"></i> New
</router-link>
</div>
</div>
</div>
<div class="ui text container">
<div class="ui one column grid">
<div class="column">
<router-view />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'app'
};
</script>
<style>
#app > div.navbar {
margin-bottom: 1.5em;
}
.myFlash {
width: 250px;
margin: 10px;
position: absolute;
top: 50;
right: 0;
}
input {
width: 300px;
}
div.label {
width: 120px;
}
div.input {
margin-bottom: 10px;
}
button.ui.button {
margin-top: 15px;
display: block;
}
</style>
TaskForm.vue 를 수정한다.
<template>
<form action="#" @submit.prevent="onSubmit">
<p v-if="errorsPresent" class="error">Please fill out both fields!</p>
<div class="ui labeled input fluid">
<div class="ui label">
<i class="calendar plus icon"></i>task
</div>
<input type="text" placeholder="Enter task..." v-model="task.task1" />
</div>
<div class="ui labeled input fluid">
<div class="ui label">
<i class="info circle icon"></i> Details
</div>
<input type="text" placeholder="Enter Details" v-model="task.task2" />
</div>
<button class="positive ui button">Submit</button>
</form>
</template>
<script>
export default {
name: 'task-form',
props: {
task: {
type: Object,
required: false,
default: () => {
return {
task1: '',
task2: ''
};
}
}
},
data() {
return {
errorsPresent: false
};
},
methods: {
onSubmit: function() {
if (this.task.task1 === '' || this.task.task2 === '') {
this.errorsPresent = true;
} else {
this.$emit('createOrUpdate', this.task);
}
}
}
};
</script>
<style scoped>
.error {
color: red;
}
</style>
New.vue를 수정한다.
<template>
<div>
<h1>New task</h1>
<task-form @createOrUpdate="createOrUpdate"></task-form>
</div>
</template>
<script>
import taskForm from '../components/TaskForm.vue';
import { api } from '../helpers/helpers';
export default {
name: 'new-task',
components: {
'task-form': taskForm
},
methods: {
createOrUpdate: async function(task) {
const res = await api.createtask(task);
this.flash('task created', 'success');
this.$router.push(`/tasks/${res._id}`);
}
}
};
</script>
Edit.vue 를 수정한다.
<template>
<div>
<h1>Edit task</h1>
<task-form @createOrUpdate="createOrUpdate" :task=this.task></task-form>
</div>
</template>
<script>
import taskForm from '../components/TaskForm.vue';
import { api } from '../helpers/helpers';
export default {
name: 'edit',
components: {
'task-form': taskForm
},
data: function() {
return {
task: {}
};
},
methods: {
createOrUpdate: async function(task) {
await api.updatetask(task);
this.flash('task updated sucessfully!', 'success');
this.$router.push(`/tasks/${task._id}`);
}
},
async mounted() {
this.task = await api.gettask(this.$route.params.id);
}
};
</script>
Show.vue를 수정한다.
<template>
<div>
<h1>Show task</h1>
<div class="ui labeled input fluid">
<div class="ui label">
<i class="tasks icon"></i> Task
</div>
<input type="text" readonly :value="task.task1"/>
</div>
<div class="ui labeled input fluid">
<div class="ui label">
<i class="info circle icon"></i> Details
</div>
<input type="text" readonly :value="task.task2"/>
</div>
<div class="actions">
<router-link :to="{ name: 'edit', params: { id: this.$route.params.id }}">
Edit task
</router-link>
</div>
</div>
</template>
<script>
import { api } from '../helpers/helpers';
export default {
name: 'show',
data() {
return {
task: ''
};
},
async mounted() {
this.task = await api.gettask(this.$route.params.id);
}
};
</script>
<style scoped>
.actions a {
display: block;
text-decoration: underline;
margin: 20px 10px;
}
</style>
Tasks.vue를 수정한다.
<template>
<div>
<h1>tasks</h1>
<table id="tasks" class="ui celled compact table">
<thead>
<tr>
<th> <i class="calendar plus icon"></i>Task</th>
<th> <i class="info circle icon"></i>Detail</th>
<th> <i class="lock open icon"></i></th>
<th> <i class="edit icon"></i></th>
<th> <i class="trash icon"></i></th>
<th colspan="3"></th>
</tr>
</thead>
<tr v-for="(task, i) in tasks" :key="i">
<td>{{ task.task1 }}</td>
<td>{{ task.task2 }}</td>
<td width="75" class="center aligned">
<router-link :to="{ name: 'show', params: { id: task._id }}">Show</router-link>
</td>
<td width="75" class="center aligned">
<router-link :to="{ name: 'edit', params: { id: task._id }}">Edit</router-link>
</td>
<td width="75" class="center aligned" @click.prevent="onDestroy(task._id)">
<a :href="`/tasks/${task._id}`">Delete</a>
</td>
</tr>
</table>
</div>
</template>
<script>
import { api } from '../helpers/helpers';
export default {
name: 'tasks',
data() {
return {
tasks: []
};
},
methods: {
async onDestroy(id) {
const sure = window.confirm('Are you sure?');
if (!sure) return;
await api.deletetask(id);
this.flash('task deleted sucessfully!', 'success');
const newtasks = this.tasks.filter(task => task._id !== id);
this.tasks = newtasks;
}
},
async mounted() {
this.tasks = await api.gettasks();
}
};
</script>
API와 통신하기 위한 helpers.js 를 수정한다.
import axios from 'axios';
import Vue from 'vue';
import VueFlashMessage from 'vue-flash-message';
import 'vue-flash-message/dist/vue-flash-message.min.css';
Vue.use(VueFlashMessage, {
messageOptions: {
timeout: 3000,
pauseOnInteract: true
}
});
const vm = new Vue();
const baseURL = 'http://localhost:3000/tasks/';
const handleError = fn => (...params) =>
fn(...params).catch(error => {
vm.flash(`${error.response.status}: ${error.response.statusText}`, 'error');
});
export const api = {
gettask: handleError(async id => {
const res = await axios.get(baseURL + id);
return res.data;
}),
gettasks: handleError(async () => {
const res = await axios.get(baseURL);
return res.data;
}),
deletetask: handleError(async id => {
const res = await axios.delete(baseURL + id);
return res.data;
}),
createtask: handleError(async payload => {
const res = await axios.post(baseURL, payload);
return res.data;
}),
updatetask: handleError(async payload => {
const res = await axios.put(baseURL + payload._id, payload);
return res.data;
})
};
이제 APP 서버를 다시 실행하고, 크롬 브라우저에 접속하면 다음과 같은 화면을 볼 수 있다.
앱 서버 실행
레퍼런스
댓글 없음:
댓글 쓰기