Xây dựng full stack web apps với MEVN Stack [Phần 2/2]
Hôm nay mình xin viết tiếp bài hướng dẫn về việc xây dựng web app với MEVN. Hôm này mình sẽ tập trung vô việc CRUD(Create, Read, Update, Delete) bằng cách sử dụng ExpressJS và MongoDB(mongoose). Ở hướng dẫn trước, mình đã làm: Tạo khung cơ bản cho ứng dụng MEVN Tạo kết nối giữa phần ...
Hôm nay mình xin viết tiếp bài hướng dẫn về việc xây dựng web app với MEVN. Hôm này mình sẽ tập trung vô việc CRUD(Create, Read, Update, Delete) bằng cách sử dụng ExpressJS và MongoDB(mongoose).
Ở hướng dẫn trước, mình đã làm:
- Tạo khung cơ bản cho ứng dụng MEVN
- Tạo kết nối giữa phần backend(ExpressJS) và frontend(VueJS)
Mình đã tạo ra ứng dụng mà khi gọi API http://localhost:8080/posts nó sẽ list ra tất cả posts bằng service PostService. Hãy chạy lại lệnh npm run dev và npm start nào. Bây giờ, hãy mở lại trang http://localhost:8080 (Update hình sau) Bắt đầu bằng việc kết nối với mongoose trước. Trong file server/app.js thêm đoạn code sau:
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/posts'); var db = mongoose.connection; db.on("error", console.error.bind(console, "connection error")); db.once("open", function(callback){ console.log("Connection Succeeded"); });
Đoạn code này sẽ tạo một kết nối với database tên là posts Bây giờ hãy tạo CRUD, ta sẽ cần một model mà thể hiện được Post trong MongoDB. Vì vậy, giờ hãy định nghĩa Schema cho model Post. Tạo một folder tên models trong folder server và thêm file post.js:
var mongoose = require("mongoose"); var Schema = mongoose.Schema; var PostSchema = new Schema({ title: String, description: String }); var Post = mongoose.model("Post", PostSchema); module.exports = Post;
Ta đã có được model Post, giờ hãy thêm nó vào server/app.js
var Post = require("../models/post");
1. CREATE
Trong server/app.js thêm:
// Add new post app.post('/posts', (req, res) => { var db = req.db; var title = req.body.title; var description = req.body.description; var new_post = new Post({ title: title, description: description }) new_post.save(function (error) { if (error) { console.log(error) } res.send({ success: true, message: 'Post saved successfully!' }) }) })
Tại đây, ta đã có một API được gọi là posts nhận phương thức httpPOST để tạo một bản ghi mới cho mô hình model Post. Ta cũng có thể thử nghiệm API này bằng cách sử dụng Postman hoặc bất kỳ công cụ nào khác mà bạn thích. Tôi đang sử dụng Postman. Ở đây, bạn sẽ có thể xem một cái gì đó như thế này: (Update hình sau)
2. READ
Mình đã có sẵn API get post từ bài trước, giờ hãy sửa nó lại như sau(trong file server/app.js)
// Fetch all posts app.get('/posts', (req, res) => { Post.find({}, 'title description', function (error, posts) { if (error) { console.error(error); } res.send({ posts: posts }) }).sort({\_id:-1}) })
đoạn code này sẽ lấy tất cả posts và xếp theo thứ tự ID giảm dần (update hình sau)
Vậy là ta đã có API để tạo và get list posts. Giờ hãy làm UI nào.
Trong client/src/router/index.js, thêm route để tạo post:
import Vue from 'vue' import Router from 'vue-router' import Hello from '@/components/Hello' import Posts from '@/components/Posts' import NewPost from '@/components/NewPost' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Hello', component: Hello }, { path: '/posts', name: 'Posts', component: Posts }, { path: '/posts/new', name: 'NewPost', component: NewPost } ] })
Tạo một file mới trong client/src/components và đặt tên là NewPost.vue, sau đó thêm đoạn code sau để có được form tạo post mới:
<template> <div class="posts"> <h1>Add Post</h1> <div class="form"> <div> <input type="text" name="title" placeholder="TITLE" v-model="title"> </div> <div> <textarea rows="15" cols="15" placeholder="DESCRIPTION" v-model="description"></textarea> </div> <div> <button class="app_post_btn" @click="addPost">Add</button> </div> </div> </div> </template> <script> import PostsService from '@/services/PostsService' export default { name: 'NewPost', data () { return { title: ', description: ' } }, methods: { async addPost () { await PostsService.addPost({ title: this.title, description: this.description }) this.$router.push({ name: 'Posts' }) } } } </script> <style type="text/css"> .form input, .form textarea { awidth: 500px; padding: 10px; border: 1px solid #e0dede; outline: none; font-size: 12px; } .form div { margin: 20px; } .app_post_btn { background: #4d7ef7; color: #fff; padding: 10px 80px; text-transform: uppercase; font-size: 12px; font-weight: bold; awidth: 520px; border: none; cursor: pointer; } </style>
Trong file client/src/services/PostsService.js:
import Api from '@/services/Api' export default { fetchPosts () { return Api().get('posts') }, addPost (params) { return Api().post('posts', params) } }
Những gì ta đã làm cho đến bây giờ là gọi là một phương thức khi người dùng nhấp chuột vào nút Add trong form, PostsService được gọi thêm các bài post bằng API đến server. Chúng ta cũng sửa đổi file view của chúng ta liệt kê tất cả các file, sửa client/src/components/Posts.vue
<template> <div class="posts"> <h1>Posts</h1> <div v-if="posts.length > 0" class="table-wrap"> <div> <router-link v-bind:to="{ name: 'NewPost' }" class="">Add Post</router-link> </div> <table> <tr> <td>Title</td> <td awidth="550">Description</td> <td awidth="100" align="center">Action</td> </tr> <tr v-for="post in posts"> <td>{{ post.title }}</td> <td>{{ post.description }}</td> <td align="center"> <router-link v-bind:to="{ name: 'EditPost', params: { id: post._id } }">Edit</router-link> | <a href="#">Delete</a> </td> </tr> </table> </div> <div v-else> There are no posts.. Lets add one now <br /><br /> <router-link v-bind:to="{ name: 'NewPost' }" class="add_post_link">Add Post</router-link> </div> </div> </template> <script> import PostsService from '@/services/PostsService' export default { name: 'posts', data () { return { posts: [] } }, mounted () { this.getPosts() }, methods: { async getPosts () { const response = await PostsService.fetchPosts() this.posts = response.data.posts } } } </script> <style type="text/css"> .table-wrap { awidth: 60%; margin: 0 auto; text-align: center; } table th, table tr { text-align: left; } table thead { background: #f2f2f2; } table tr td { padding: 10px; } table tr:nth-child(odd) { background: #f2f2f2; } table tr:nth-child(1) { background: #4d7ef7; color: #fff; } a { color: #4d7ef7; text-decoration: none; } a.add_post_link { background: #4d7ef7; color: #fff; padding: 10px 80px; text-transform: uppercase; font-size: 12px; font-weight: bold; } </style>
Bây giờ hãy vào lại trang http://localhost:8080/posts để xem: (update hình sau)
Vậy là ta đã hoàn thành phần CREATE và READ. Giờ chuyển tiếp đến phần UPDATE và DELETE nào.
3. UPDATE
Trong server/app.js, thêm:
// Fetch single post app.get('/post/:id', (req, res) => { var db = req.db; Post.findById(req.params.id, 'title description', function (error, post) { if (error) { console.error(error); } res.send(post) }) }) // Update a post app.put('/posts/:id', (req, res) => { var db = req.db; Post.findById(req.params.id, 'title description', function (error, post) { if (error) { console.error(error); } post.title = req.body.title post.description = req.body.description post.save(function (error) { if (error) { console.log(error) } res.send({ success: true }) }) }) })
Thêm route vào client/src/router/index.js
import Vue from 'vue' import Router from 'vue-router' import Hello from '@/components/Hello' import Posts from '@/components/Posts' import NewPost from '@/components/NewPost' import EditPost from '@/components/EditPost' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Hello', component: Hello }, { path: '/posts', name: 'Posts', component: Posts }, { path: '/posts/new', name: 'NewPost', component: NewPost }, { path: '/posts/:id', name: 'EditPost', component: EditPost } ] })
Và thêm component EditPost.vue vào:
<template> <div class="posts"> <h1>Edit Post</h1> <div class="form"> <div> <input type="text" name="title" placeholder="TITLE" v-model="title"> </div> <div> <textarea rows="15" cols="15" placeholder="DESCRIPTION" v-model="description"></textarea> </div> <div> <button class="app_post_btn" @click="updatePost">Update</button> </div> </div> </div> </template> <script> import PostsService from '@/services/PostsService' export default { name: 'EditPost', data () { return { title: ', description: ' } }, mounted () { this.getPost() }, methods: { async getPost () { const response = await PostsService.getPost({ id: this.$route.params.id }) this.title = response.data.title this.description = response.data.description }, async updatePost () { await PostsService.updatePost({ id: this.$route.params.id, title: this.title, description: this.description }) this.$router.push({ name: 'Posts' }) } } } </script> <style type="text/css"> .form input, .form textarea { awidth: 500px; padding: 10px; border: 1px solid #e0dede; outline: none; font-size: 12px; } .form div { margin: 20px; } .app_post_btn {