How to create Restful CRUD API with Node.js MongoDB and Express.js
In this blog, we are going to learn how to perform CRUD (Create, Read, Update and Delete) operations with the help of Rest API using Node.js, MongoDB as our database, and Expess.
RESTIn simple terms, REST stands for Representational State Transfer. It is an architectural style design for distributed hypermedia, or an Application Programming Interface (API). In REST, we use various standard HTTP
methods like GET
, POST
, PUT
and DELETE
to perform any CRUD
operation on resource.
In REST
, everything is Resource
. A resource can be an image, document, a temporary service, a collection of other resource, and any other object. Each resource has resource identifier to identify it.
As per REST
guidelines, we should use only HTTP
methods to perform CRUD
operation on any resource. In this blog, we are going to use 4 HTTP methods like GET
, POST
, PUT
and DELETE
to make our REST
API.
Let’s have a brief introduction of each http method here.
The HTTP GET
is used to Read
or Retrieve
any resource. It returns the XML or JSON data with HTTP status code of 200. GET method is considered as safe, because we are just getting or reading the resource data, not doing any changes in the resource data.
The HTTP POST
is used to Create
a new resource. On successful creation of resource, it will return HTTP status code of 201, a Location header with a link to the newly created resource.
The HTTP PUT
is used to Update
any existing resource. On successful, it will return HTTP status code of 200.
The HTTP DELETE
, as the name suggests, is used to Delete
any existing resource. On successful, it will return HTTP status code of 200.
Let’s move forward into the details of other pieces of creating our REST API.
Express.jsWe are going to use Express.js or simply Express. It is a web application framework for Node.js. It has been released as free and open source software. You can create web application and APIs using Express. It has support for routing, middleware, view system etc.
MongooseMongoose is Object Document Mapping or ODM tool for Node.js and MongoDB. Mongoose provide a straight-forward, schema based solution to model to your application data. It includes built-in type casting, validation, query building, business logic hooks and many more.
PrerequisitesYou must have Node.js and MongoDB installed on your machine. Click the below links, if you don’t have any one of them.
For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.
Database-as-a-Service for MongoDB
Install Postman – Google Chrome for testing purpose.
After setting up prerequisites, let move forward to build our application.
Application IntroductionIn our application, we are going to create a product based application. We will use REST APIs to create, update, get and delete the product. Let’s go to create our application.
Let’s create a folder, and start with creating package.json
file first. Use this command in your terminal window.
For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.#### package.json
{
"name": "product-app",
"version": "1.0.0",
"description": "This is zepbook product app",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "ZeptoBook",
"license": "MIT"
}
If you notice line 5, we have defined server.js
as our main entry point.
Let’s install all the express
, mongoose
and body-parser
package dependencies in our app.
For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.
Once these packages installed successfully, ourpackage.json
file will be updated automatically. Our latest file will be like this.
{
"name": "product-app",
"version": "1.0.0",
"description": "This is zepbook product app",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "ZeptoBook",
"license": "MIT",
"dependencies": {
"body-parser": "^1.18.3",
"express": "^4.16.4",
"mongoose": "^5.4.2"
}
}
Notice dependencies
section of our file, all these packages are mentioned there.
Let’s create a server.js
file in the root directory of the application.
// get dependencies
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
// parse requests
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
// default route
app.get('/', (req, res) => {
res.json({"message": "Welcome to ZeptoBook Product app"});
});
// listen on port 3000
app.listen(3000, () => {
console.log("Server is listening on port 3000");
});
Let’s briefly review our above code. First of all, we imported the required dependencies in our server.js file.
It is Node.js body parser middleware. It parse the incoming request bodies in a middleware before your handlers, available under the req.body
property.
Learn more about bodyParser.urlencoded([options])
Learn more about bodyParser.json([options])
Then, we define a default route using GET Http method. By default, it will return our message on default url.
Finally, we are going to listen all incoming requests on port 3000.
Once everything is all set, let’s wake up our server by running this command in our terminal window.
For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.> For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.####
Let’s create a config file in our app, where we can define various constants like dbconnection or port number instead of hard-coded it everywhere. So, create a config.js
file in your app folder.
module.exports = { url: 'mongodb://<dbUserName>:<dbUserPassword>@ds251002.mlab.com:51002/adeshtestdb',
serverport: 3000 }
So, here I mentioned two constants in config.js
file.
Let’s connect with our MongoDb database. Add these lines of codes in server.js
file after the app.use(bodyParser.json());
// Configuring the database
const config = require('./config.js');
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
// Connecting to the database
mongoose.connect(config.url, {
useNewUrlParser: true
}).then(() => {
console.log("Successfully connected to the database");
}).catch(err => {
console.log('Could not connect to the database. Exiting now...', err);
process.exit();
});
Also, we are going to replace our hard-coded server port with our config constant in server.js file
// listen on port 3000
app.listen(config.serverport, () => {
console.log("Server is listening on port 3000");
});
Here, you can see, we are now using config.serverport
in app.listen().
Now, run again the server using this command.
For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.> For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.> For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.####
Let’s create a product
model in our app folder in order to save the data in our db. Create a product.model.js
file in your app.
const mongoose = require('mongoose');
const ProductSchema = mongoose.Schema({
title: String,
description: String,
price: Number,
company: String
}, {
timestamps: true
});
module.exports = mongoose.model('Products', ProductSchema);
Here, we have defined our ProductSchema
with following properties. Along with this, we also set timestamps
property to true
. This property will add two fields automatically to schema. These fields are : createdAt
and updatedAt
in your schema.
We are going to write all functions related to create, retrieve, update and delete products in our controller file. Let’s create a controller file named product.controller.js
in your app folder.
const Product = require('./product.model.js');
//Create new Product
exports.create = (req, res) => {
// Request validation
if(!req.body) {
return res.status(400).send({
message: "Product content can not be empty"
});
}
// Create a Product
const product = new Product({
title: req.body.title || "No product title",
description: req.body.description,
price: req.body.price,
company: req.body.company
});
// Save Product in the database
product.save()
.then(data => {
res.send(data);
}).catch(err => {
res.status(500).send({
message: err.message || "Something wrong while creating the product."
});
});
};
// Retrieve all products from the database.
exports.findAll = (req, res) => {
Product.find()
.then(products => {
res.send(products);
}).catch(err => {
res.status(500).send({
message: err.message || "Something wrong while retrieving products."
});
});
};
// Find a single product with a productId
exports.findOne = (req, res) => {
Product.findById(req.params.productId)
.then(product => {
if(!product) {
return res.status(404).send({
message: "Product not found with id " + req.params.productId
});
}
res.send(product);
}).catch(err => {
if(err.kind === 'ObjectId') {
return res.status(404).send({
message: "Product not found with id " + req.params.productId
});
}
return res.status(500).send({
message: "Something wrong retrieving product with id " + req.params.productId
});
});
};
// Update a product
exports.update = (req, res) => {
// Validate Request
if(!req.body) {
return res.status(400).send({
message: "Product content can not be empty"
});
}
// Find and update product with the request body
Product.findByIdAndUpdate(req.params.productId, {
title: req.body.title || "No product title",
description: req.body.description,
price: req.body.price,
company: req.body.company
}, {new: true})
.then(product => {
if(!product) {
return res.status(404).send({
message: "Product not found with id " + req.params.productId
});
}
res.send(product);
}).catch(err => {
if(err.kind === 'ObjectId') {
return res.status(404).send({
message: "Product not found with id " + req.params.productId
});
}
return res.status(500).send({
message: "Something wrong updating note with id " + req.params.productId
});
});
};
// Delete a note with the specified noteId in the request
exports.delete = (req, res) => {
Product.findByIdAndRemove(req.params.productId)
.then(product => {
if(!product) {
return res.status(404).send({
message: "Product not found with id " + req.params.productId
});
}
res.send({message: "Product deleted successfully!"});
}).catch(err => {
if(err.kind === 'ObjectId' || err.name === 'NotFound') {
return res.status(404).send({
message: "Product not found with id " + req.params.productId
});
}
return res.status(500).send({
message: "Could not delete product with id " + req.params.productId
});
});
};
Next step is to create our api routes. Create a product.routes.js
file in your app folder.
module.exports = (app) => {
const products = require('./product.controller.js');
// Create a new Product
app.post('/products', products.create);
// Retrieve all Products
app.get('/products', products.findAll);
// Retrieve a single Product with productId
app.get('/products/:productId', products.findOne);
// Update a Note with productId
app.put('/products/:productId', products.update);
// Delete a Note with productId
app.delete('/products/:productId', products.delete);
}
Note: import this route file in our server.js file after these lines. See line 4 in the below code.
// Configuring the database
const config = require('./config.js');
const mongoose = require('mongoose');
require('./product.routes.js')(app); //Add route file here
If you try to access your api routes through your client-side app, you might face Access-Control-Allow-Origin
error messages. So, in order to avoid these message, we are also enabling CORS
in our server.js
file.
//Enable CORS for all HTTP methods
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
So, this will be our final server.js
file.
// get dependencies
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
// parse requests
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//Enable CORS for all HTTP methods
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
// Configuring the database
const config = require('./config.js');
const mongoose = require('mongoose');
require('./product.routes.js')(app);
mongoose.Promise = global.Promise;
// Connecting to the database
mongoose.connect(config.url, {
useNewUrlParser: true
}).then(() => {
console.log("Successfully connected to the database");
}).catch(err => {
console.log('Could not connect to the database. Exiting now...', err);
process.exit();
});
// default route
app.get('/', (req, res) => {
res.json({"message": "Welcome to ZeptoBook Product app"});
});
// listen on port 3000
app.listen(config.serverport, () => {
console.log("Server is listening on port 3000");
});
This will be our project structure.

Now, it’s time to test our all REST APIs for CRUD Operation.
I have added few products in the database. See the below screen shot.

Before Update
After Update
In this tutorial, you'll learn how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB and how to build application with Node.js, ExpressJs, MongoDB and Vue.js
In this tutorial, you'll learn how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB and how to build application with Node.js, ExpressJs, MongoDB and Vue.js
Vue.js is a JavaScript framework with growing number of users. Released 4 years ago, it’s now one of the most populare front-end frameworks. There are some reasons why people like Vue.js. Using Vue.js is very simple if you are already familiar with HTML and JavaScript. They also provide clear documentation and examples, makes it easy for starters to learn the framework. Vue.js can be used for both simple and complex applications. If your application is quite complex, you can use Vuex for state management, which is officially supported. In addition, it’s also very flexible that yu can write template in HTML, JavaScript or JSX.
This tutorial shows you how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB. As for example, we’re going to create a simple application for managing posts which includes list posts, create post, update post and delete post (basic CRUD functionality). I divide this tutorial into two parts. The first part is setting up the Node.js back-end and database. The other part is writing Vue.js code including how to build .vue code using Webpack.
DependenciesThere are some dependencies required for this project. Add the dependencies below to your package.json. Then run npm install
to install these dependencies.
"dependencies": {
"body-parser": "~1.17.2",
"dotenv": "~4.0.0",
"express": "~4.16.3",
"lodash": "~4.17.10",
"mongoose": "~5.2.9",
"morgan": "~1.9.0"
},
"devDependencies": {
"axios": "~0.18.0",
"babel-core": "~6.26.3",
"babel-loader": "~7.1.5",
"babel-preset-env": "~1.7.0",
"babel-preset-stage-3": "~6.24.1",
"bootstrap-vue": "~2.0.0-rc.11",
"cross-env": "~5.2.0",
"css-loader": "~1.0.0",
"vue": "~2.5.17",
"vue-loader": "~15.3.0",
"vue-router": "~3.0.1",
"vue-style-loader": "~4.1.2",
"vue-template-compiler": "~2.5.17",
"webpack": "~4.16.5",
"webpack-cli": "^3.1.0"
},
Project Structure
Below is the overview of directory structure for this project.
app
config
controllers
models
queries
routes
views
public
dist
src
The app
directory contains all files related to server-side. The public
directory contains two sub-directories: dist
and src
. dist
is used for the output of build result, while src
is for front-end code files.
First, we define a model for Post using Mongoose. To make it simple, it only has two properties: title and content.
app/models/Post.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
const PostSchema = new Schema(
{
title: { type: String, trim: true, index: true, default: '' },
content: { type: String },
},
{
collection: 'posts',
timestamps: true,
},
);
module.exports = mongoose.model('Post', PostSchema);
After defining the model, we write some queries that will be needed in the controllers.
app/queries/posts.js
const Post = require('../models/Post');
/**
* Save a post.
*
* @param {Object} post - Javascript object or Mongoose object
* @returns {Promise.}
*/
exports.save = (post) => {
if (!(post instanceof Post)) {
post = new Post(post);
}
return post.save();
};
/**
* Get post list.
* @param {object} [criteria] - Filter options
* @returns {Promise.<Array.>}
*/
exports.getPostList = (criteria = {}) => Post.find(criteria);
/**
* Get post by ID.
* @param {string} id - Post ID
* @returns {Promise.}
*/
exports.getPostById = id => Post.findOne({ _id: id });
/**
* Delete a post.
* @param {string} id - Post ID
* @returns {Promise}
*/
exports.deletePost = id => Post.findByIdAndRemove(id);
We need API controllers for handling create post, get post listing, get detail of a post, update a post and delete a post.
app/controllers/api/posts/create.js
const postQueries = require('../../../queries/posts');
module.exports = (req, res) => postQueries.save(req.body)
.then((post) => {
if (!post) {
return Promise.reject(new Error('Post not created'));
}
return res.status(200).send(post);
})
.catch((err) => {
console.error(err);
return res.status(500).send('Unable to create post');
});
app/controllers/api/posts/delete.js
const postQueries = require('../../../queries/posts');
module.exports = (req, res) => postQueries.deletePost(req.params.id)
.then(() => res.status(200).send())
.catch((err) => {
console.error(err);
return res.status(500).send('Unable to delete post');
});
app/controllers/api/posts/details.js
const postQueries = require('../../../queries/posts');
module.exports = (req, res) => postQueries.getPostById(req.params.id)
.then((post) => {
if (!post) {
return Promise.reject(new Error('Post not found'));
}
return res.status(200).send(post);
})
.catch((err) => {
console.error(err);
return res.status(500).send('Unable to get post');
});
app/controllers/api/posts/list.js
const postQueries = require('../../../queries/posts');
module.exports = (req, res) => postQueries.getPostList(req.params.id)
.then(posts => res.status(200).send(posts))
.catch((err) => {
console.error(err);
return res.status(500).send('Unable to get post list');
});
app/controllers/api/posts/update.js
const _ = require('lodash');
const postQueries = require('../../../queries/posts');
module.exports = (req, res) => postQueries.getPostById(req.params.id)
.then(async (post) => {
if (!post) {
return Promise.reject(new Error('Post not found'));
}
const { title, content } = req.body;
_.assign(post, {
title, content
});
await postQueries.save(post);
return res.status(200).send({
success: true,
data: post,
})
})
.catch((err) => {
console.error(err);
return res.status(500).send('Unable to update post');
});
We need to have some pages for user interaction and some API endpoints for processing HTTP requests. To make the app scalable, it’s better to separate the routes for pages and APIs.
app/routes/index.js
const express = require('express');
const routes = express.Router();
routes.use('/api', require('./api'));
routes.use('/', require('./pages'));
module.exports = routes;
Below is the API routes.
app/routes/api/index.js
const express = require('express');
const router = express.Router();
router.get('/posts/', require('../../controllers/api/posts/list'));
router.get('/posts/:id', require('../../controllers/api/posts/details'));
router.post('/posts/', require('../../controllers/api/posts/create'));
router.patch('/posts/:id', require('../../controllers/api/posts/update'));
router.delete('/posts/:id', require('../../controllers/api/posts/delete'));
module.exports = router;
For the pages, in this tutorial, we use plain HTML file. You can easily replace it with any HTML template engine if you want. The HTML file contains a div whose id is app. Later, in Vue.js application, it will use the element with id app for rendering the content. What will be rendered on each pages is configured on Vue.js route on part 2 of this tutorial.
app/routes/pages/index.js
const express = require('express');
const router = express.Router();
router.get('/posts/', (req, res) => {
res.sendFile(`${__basedir}/views/index.html`);
});
router.get('/posts/create', (req, res) => {
res.sendFile(`${__basedir}/views/index.html`);
});
router.get('/posts/:id', (req, res) => {
res.sendFile(`${__basedir}/views/index.html`);
});
module.exports = router;
Below is the HTML file
app/views/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>VueJS Tutorial by Woolha.com</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" type="text/css" media="all" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
</head>
<body>
<div id="app"></div>
<script src="/dist/js/main.js"></script>
</body>
</html>
Below is the main script of the application, you need to run this for starting the server-side application.
app/index.js
require('dotenv').config();
const bodyParser = require('body-parser');
const express = require('express');
const http = require('http');
const mongoose = require('mongoose');
const morgan = require('morgan');
const path = require('path');
const dbConfig = require('./config/database');
const routes = require('./routes');
const app = express();
const port = process.env.PORT || 4000;
global.__basedir = __dirname;
mongoose.Promise = global.Promise;
mongoose.connect(dbConfig.url, dbConfig.options, (err) => {
if (err) {
console.error(err.stack || err);
}
});
/* General setup */
app.use(morgan('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(morgan('dev'));
app.use('/', routes);
const MAX_AGE = 86400000;
// Select which directories or files under public can be served to users
app.use('/', express.static(path.join(__dirname, '../public'), { maxAge: MAX_AGE }));
// Error handler
app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
res.status(err.status || 500);
if (err.status === 404) {
res.locals.page = {
title: 'Not Found',
noIndex: true,
};
console.error(`Not found: ${req.url}`);
return res.status(404).send();
}
console.error(err.stack || err);
return res.status(500).send();
});
http
.createServer(app)
.listen(port, () => {
console.info(`HTTP server started on port ${port}`);
})
.on('error', (err) => {
console.error(err.stack || err);
});
process.on('uncaughtException', (err) => {
if (err.name === 'MongoError') {
mongoose.connection.emit('error', err);
} else {
console.error(err.stack || err);
}
});
module.exports = app;
That’s all for the server side preparation. On the next part, we’re going to set up the Vue.js client-side application and build the code into a single JavaScript file ready to be loaded from HTML.
Then, we build the code using Webpack, so that it can be loaded from HTML. In this tutorial, we’re building a simple application with basic CRUD functionality for managing posts.
Create Vue.js ComponentsFor managing posts, there are three components we’re going to create. The first one is for creating a new post. The second is for editing a post. The other is for managing posts (displaying list of posts and allow post deletion)
First, this is the component for creating a new post. It has one method createPost which validate data and send HTTP request to the server. We use axios
for sending HTTP request.
public/src/components/Posts/Create.vue
<template>
<b-container>
<h1 class="d-flex justify-content-center">Create a Post</h1>
<p v-if="errors.length">
<b>Please correct the following error(s):</b>
<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
</p>
<b-form @submit.prevent>
<b-form-group>
<b-form-input type="text" class="form-control" placeholder="Title of the post" v-model="post.title"></b-form-input>
</b-form-group>
<b-form-group>
<b-form-textarea class="form-control" placeholder="Write the content here" v-model="post.content"></b-form-textarea>
</b-form-group>
<b-button variant="primary" v-on:click="createPost">Create Post</b-button>
</b-form>
</b-container>
</template>
<script>
import axios from 'axios';
export default {
data: () => ({
errors: [],
post: {
title: '',
content: '',
},
}),
methods: {
createPost(event) {
if (event) {
event.preventDefault();
}
if (!this.post.title) {
this.errors = [];
if (!this.post.title) {
this.errors.push('Title required.');
}
return;
}
const url = 'http://localhost:4000/api/posts';
const param = this.post;
axios
.post(url, param)
.then((response) => {
console.log(response);
window.location.href = 'http://localhost:4000/posts';
}).catch((error) => {
console.log(error);
});
},
}
}
</script>
Below is the component for editing a post. Of course, we need the current data of the post before editing it. Therefore, there’s fetchPost method called when the component is created. There’s also updatePost method which validate data and call the API for updating post.
public/src/components/Posts/Edit.vue
<template>
<b-container>
<h1 class="d-flex justify-content-center">Edit a Post</h1>
<p v-if="errors.length">
<b>Please correct the following error(s):</b>
<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
</p>
<b-form @submit.prevent>
<b-form-group>
<b-form-input type="text" class="form-control" placeholder="Title of the post" v-model="post.title"></b-form-input>
</b-form-group>
<b-form-group>
<b-form-textarea class="form-control" placeholder="Write the content here" v-model="post.content"></b-form-textarea>
</b-form-group>
<b-button variant="primary" v-on:click="updatePost">Update Post</b-button>
</b-form>
</b-container>
</template>
<script>
import axios from 'axios';
export default {
data: () => ({
errors: [],
post: {
_id: '',
title: '',
content: '',
},
}),
created: function() {
this.fetchPost();
},
methods: {
fetchPost() {
const postId = this.$route.params.id;
const url = `http://localhost:4000/api/posts/${postId}`;
axios
.get(url)
.then((response) => {
this.post = response.data;
console.log('this.post;');
console.log(this.post);
});
},
updatePost(event) {
if (event) {
event.preventDefault();
}
if (!this.post.title) {
this.errors = [];
if (!this.post.title) {
this.errors.push('Title required.');
}
return;
}
const url = `http://localhost:4000/api/posts/${this.post._id}`;
const param = this.post;
axios
.patch(url, param)
.then((response) => {
console.log(response);
window.alert('Post successfully saved');
}).catch((error) => {
console.log(error);
});
},
}
}
</script>
For managing posts, we need to fetch the list of post first. Similar to the edit component, in this component, we have fetchPosts method called when the component is created. For deleting a post, there’s also a method deletePost. If post successfully deleted, the fetchPosts method is called again to refresh the post list.
public/src/components/Posts/List.vue
<template>
<b-container>
<h1 class="d-flex justify-content-center">Post List</h1>
<b-button variant="primary" style="color: #ffffff; margin: 20px;"><a href="/posts/create" style="color: #ffffff;">Create New Post</a></b-button>
<b-container-fluid v-if="posts.length">
<table class="table">
<thead>
<tr class="d-flex">
<td class="col-8">Titleqqqqqqqqq</td>
<td class="col-4">Actions</td>
</tr>
</thead>
<tbody>
<tr v-for="post in posts" class="d-flex">
<td class="col-8">{{ post.title }}</td>
<td class="col-2"><a v-bind:href="'http://localhost:4000/posts/' + post._id"><button type="button" class="btn btn-primary"><i class="fa fa-edit" aria-hidden="true"></i></button></a></td>
<td class="col-2"><button type="button" class="btn btn-danger" v-on:click="deletePost(post._id)"><i class="fa fa-remove" aria-hidden="true"></i></button></td>
</tr>
</tbody>
</table>
</b-container-fluid>
</b-container>
</template>
<script>
import axios from 'axios';
export default {
data: () => ({
posts: [],
}),
created: () => {
this.fetchPosts();
},
methods: {
fetchPosts() {
const url = 'http://localhost:4000/api/posts/';
axios
.get(url)
.then((response) => {
console.log(response.data);
this.posts = response.data;
});
},
deletePost(id) {
if (event) {
event.preventDefault();
}
const url = `http://localhost:4000/api/posts/${id}`;
const param = this.post;
axios
.delete(url, param)
.then((response) => {
console.log(response);
console.log('Post successfully deleted');
this.fetchPosts();
}).catch((error) => {
console.log(error);
});
},
}
}
</script>
All of the components above are wrapped into a root component which roles as the basic template. The root component renders the navbar which is same across all components. The component for each routes will be rendered on router-view
.
public/src/App.vue
<template>
<div>
<b-navbar toggleable="md" type="dark" variant="dark">
<b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
<b-navbar-brand to="/">My Vue App</b-navbar-brand>
<b-collapse is-nav id="nav_collapse">
<b-navbar-nav>
<b-nav-item to="/">Home</b-nav-item>
<b-nav-item to="/posts">Manage Posts</b-nav-item>
</b-navbar-nav>
</b-collapse>
</b-navbar>
<!-- routes will be rendered here -->
<router-view />
</div>
</template>
<script>
export default {
name: 'app',
data () {},
methods: {}
}
</script>
For determining which component should be rendered, we use Vue.js’ router. For each routes, we need to define the path, component name and the component itself. A component will be rendered if the current URL matches the path.
public/src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import CreatePost from '../components/Posts/Create.vue';
import EditPost from '../components/Posts/Edit.vue';
import ListPost from '../components/Posts/List.vue';
Vue.use(Router);
let router = new Router({
mode: 'history',
routes: [
{
path: '/posts',
name: 'ListPost',
component: ListPost,
},
{
path: '/posts/create',
name: 'CreatePost',
component: CreatePost,
},
{
path: '/posts/:id',
name: 'EditPost',
component: EditPost,
},
]
});
export default router;
Lastly, we need a main script as the entry point which imports the main App component and the router. Inside, it creates a new Vue instance
webpack.config.js
import BootstrapVue from 'bootstrap-vue';
import Vue from 'vue';
import App from './App.vue';
import router from './router';
Vue.use(BootstrapVue);
Vue.config.productionTip = false;
new Vue({
el: '#app',
router,
render: h => h(App),
});
Configure Webpack
For building the code into a single JavaSript file. Below is the basic configuration for Webpack 4.
webpack.config.js
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
entry: './public/src/main.js',
output: {
path: `${__dirname}/public/dist/js/`,
filename: '[name].js',
},
resolve: {
modules: [
'node_modules',
],
alias: {
// vue: './vue.js'
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
]
},
plugins: [
new VueLoaderPlugin(),
]
After that, run ./node_modules/webpack/bin/webpack.js
. You can add the command to the scripts
section of package.json
, so you can run Webpack with a shorter command npm run build
, as examplified below.
"dependencies": {
...
},
"devDependencies": {
...
},
"scripts": {
"build": "./node_modules/webpack/bin/webpack.js",
"start": "node app/index.js"
},
Finally, you can start to try the application. This code is also available on Woolha.com’s Github.
Node.js is an open-source, cross-platform, runtime environment that allows developers to run JavaScript outside of a browser. In this post, you'll see top 7 of the most popular Node frameworks at this point in time (ranked from high to low by GitHub stars).
Node.js is an open-source, cross-platform, runtime environment that allows developers to run JavaScript outside of a browser.
One of the main advantages of Node is that it enables developers to use JavaScript on both the front-end and the back-end of an application. This not only makes the source code of any app cleaner and more consistent, but it significantly speeds up app development too, as developers only need to use one language.
Node is fast, scalable, and easy to get started with. Its default package manager is npm, which means it also sports the largest ecosystem of open-source libraries. Node is used by companies such as NASA, Uber, Netflix, and Walmart.
But Node doesn't come alone. It comes with a plethora of frameworks. A Node framework can be pictured as the external scaffolding that you can build your app in. These frameworks are built on top of Node and extend the technology's functionality, mostly by making apps easier to prototype and develop, while also making them faster and more scalable.
Below are 7of the most popular Node frameworks at this point in time (ranked from high to low by GitHub stars).
ExpressWith over 43,000 GitHub stars, Express is the most popular Node framework. It brands itself as a fast, unopinionated, and minimalist framework. Express acts as middleware: it helps set up and configure routes to send and receive requests between the front-end and the database of an app.
Express provides lightweight, powerful tools for HTTP servers. It's a great framework for single-page apps, websites, hybrids, or public HTTP APIs. It supports over fourteen different template engines, so developers aren't forced into any specific ORM.
MeteorMeteor is a full-stack JavaScript platform. It allows developers to build real-time web apps, i.e. apps where code changes are pushed to all browsers and devices in real-time. Additionally, servers send data over the wire, instead of HTML. The client renders the data.
The project has over 41,000 GitHub stars and is built to power large projects. Meteor is used by companies such as Mazda, Honeywell, Qualcomm, and IKEA. It has excellent documentation and a strong community behind it.
KoaKoa is built by the same team that built Express. It uses ES6 methods that allow developers to work without callbacks. Developers also have more control over error-handling. Koa has no middleware within its core, which means that developers have more control over configuration, but which means that traditional Node middleware (e.g. req, res, next) won't work with Koa.
Koa already has over 26,000 GitHub stars. The Express developers built Koa because they wanted a lighter framework that was more expressive and more robust than Express. You can find out more about the differences between Koa and Express here.
SailsSails is a real-time, MVC framework for Node that's built on Express. It supports auto-generated REST APIs and comes with an easy WebSocket integration.
The project has over 20,000 stars on GitHub and is compatible with almost all databases (MySQL, MongoDB, PostgreSQL, Redis). It's also compatible with most front-end technologies (Angular, iOS, Android, React, and even Windows Phone).
NestNest has over 15,000 GitHub stars. It uses progressive JavaScript and is built with TypeScript, which means it comes with strong typing. It combines elements of object-oriented programming, functional programming, and functional reactive programming.
Nest is packaged in such a way it serves as a complete development kit for writing enterprise-level apps. The framework uses Express, but is compatible with a wide range of other libraries.
LoopBackLoopBack is a framework that allows developers to quickly create REST APIs. It has an easy-to-use CLI wizard and allows developers to create models either on their schema or dynamically. It also has a built-in API explorer.
LoopBack has over 12,000 GitHub stars and is used by companies such as GoDaddy, Symantec, and the Bank of America. It's compatible with many REST services and a wide variety of databases (MongoDB, Oracle, MySQL, PostgreSQL).
HapiSimilar to Express, hapi serves data by intermediating between server-side and client-side. As such, it's can serve as a substitute for Express. Hapi allows developers to focus on writing reusable app logic in a modular and prescriptive fashion.
The project has over 11,000 GitHub stars. It has built-in support for input validation, caching, authentication, and more. Hapi was originally developed to handle all of Walmart's mobile traffic during Black Friday.
In this Node.js tutorial, you'll learn how to build a modern app using Nest.js, MongoDB and Vue.js. Learn how to create a simple customer list management application by using Nest.js and Vue.js. Here, you use Nest.js to build a RESTful backend API and then leverage on Vue.js to craft a client that consumes the API.
In this Node.js tutorial, you'll learn how to build a modern app using Nest.js, MongoDB and Vue.js. Learn how to create a simple customer list management application by using Nest.js and Vue.js. Here, you use Nest.js to build a RESTful backend API and then leverage on Vue.js to craft a client that consumes the API.
Nest.js introduces a modern way of building Node.js apps by giving it a proper and modular structure out of the box. It was fully built with TypeScript but still preserves compatibility with plain JavaScript.
In this post, I will introduce and explain the fundamental steps to follow in order to combine this awesome framework with a modern frontend JavaScript framework such as Vue.js. You will build a web application to manage customers information. The application will be used to create a new customer, add several details about the customer and update each customer’s records in the database.
The approach to this post will be to build a separate REST API backend with Nest.js and a frontend to consume this API using Vue.js. So basically, instead of building Nest.js application that uses a Node.js template engine for the client side, you will leverage on the awesomeness of Vue.js as a progressive JavaScript library to quickly render contents and handled every client-side related logic.
In addition, you will use MongoDB database to persist and retrieve data for the application. MongoDB is a schema-less NoSQL document database that can receive and store data in JSON document format. It is often use with Mongoose; an Object Data Modeling (ODM) library, that helps to manage relationships between data and provides schema validations. You learn more about this later in this tutorial.
PrerequisitesA reasonable knowledge of building applications with JavaScript is required and a basic knowledge of TypeScript will be an added advantage.
Ensure that you have Node and npm installed on your local system. Check this link for Node and here for instructions on how to install npm.
Install MongoDB on your local system. Follow the instructions here to download and installed it for your choice of operating system. This tutorial uses MacOS machine for development. To successfully install MongoDB, you can either install it by using homebrew on Mac or by downloading it from the MongoDB website.
A text editor installed, such as Visual Studio Code, Atom, or Sublime Text
Nest.js has a reputation of bringing design patterns and mature structure to node.js world. This makes it quite easy to use it as the right tool for building awesome web applications. For the fact that Nest.js uses express library under the hood.
Nest.js is fully featured and structured to support MVC (Model-View-Controller) design pattern.
This means, you can install one of the popular template engine used in node.js and configure it to handle the flow of the application and interaction with backend API from the front end.
While this might be sufficient for a small app, it is always better to consider a much better and contemporary approach to handling frontend related part of an application by leveraging on a tool like Vue.js. Vue can be used to set up the frontend logic of your application as you will see later in this post.
Vue.js is a progressive javaScript framework for building reusable components for user interfaces. It is simple and yet very powerful and ideal for any project. This makes it seamless to use for a Nest.js application for example.
As you proceed in this tutorial, you will learn how to use and successfully combine these two tools, that is, Nest.js and Vue.js to build highly interactive web app.
What you’ll buildAs mentioned earlier in this post, you will build a customer list management application. To keep things really simple here, we will not be implementing authentication and authorization for any user. The main objective is for you to get conversant and comfortable using both Nest.js and Vue.js. At the end of the day, you would have learnt means to craft and structure this application as shown below:
We will use Nest.js to develop the backend API and then a Vue.js application to build components for creating, editing, deleting and showing the total list of customers from a mongoDB database.
Installing Nest.js and its dependenciesNow that the basic introductory contents have been properly covered, you will proceed to installing Nest.js and its required dependencies. Getting Nest.js installed can be done in two different ways:
You will use the Nest CLI here in this tutorial to easily craft a new Nest.js project. This comes with a lot of benefits like scaffolding a new project seamlessly, generating different files by using the nest
command amongst other things.
First, you need to install the CLI globally on your system. To do that, run the following command from the terminal:
npm install -g @nestjs/cli
The installation of this tool will give you access to the nest
command to manage the project and create Nest.js application related files as you will see later in this post.
Now that you have the CLI installed, you can now proceed to create the project for this tutorial by running the following command from your local development folder:
nest new customer-list-app-backend
The preceding command will generate a customer-list-app-backend
application. Next, change directory into this new project and install the only server dependency for the backend. As mentioned earlier, you will use MongoDB database to persist data for the application. To integrate it with a Nest.js project, you need to install mongoose and the mongoose package built by the Nest.js team. Use the following command for this purpose:
cd customer-list-app-backend
npm install --save @nestjs/mongoose mongoose
Start the application
With the installation process properly covered, you can now start the application with:
npm run start
This will start the application on the default port of 3000
. Navigate to http://localhost:3000 from your favorite browser and you should have a page similar to this:
It is assumed that by now, you have installed MongoDB on your machine as instructed at the beginning of this post. To start the database, open a new terminal so that the application keeps running and run sudo mongod
. The preceding command will start the MongoDB service and simultaneously run the database in the background.
Next, to quickly setup a connection to the database from your application, you will have to import the installed MongooseModule
within the root ApplicationModule
. To do this, use your preferred code editor to open the project and locate ./src/app.module.ts
. Update the contents with the following:
// ./src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/customer-app', { useNewUrlParser: true })
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Here, Mongoose module for MongoDB uses the forRoot()
method to supply the connection to the database.
To properly structure the kind of data that will be stored in the database for this application, you will create a database schema, a TypeScript and a data transfer object (DTO).
Here, you will create a mongoose database schema that will determine the data that should be stored in the database. To begin, navigate to the ./src/
folder and first, create a new folder named customer
and within the newly created folder, create another one and call it schemas
. Now create a new file within the schemas and named customer.schema.ts
. Open this newly created file and paste the following code in it:
// ./src/customer/schemas/customer.schema.ts
import * as mongoose from 'mongoose';
export const CustomerSchema = new mongoose.Schema({
first_name: String,
last_name: String,
email: String,
phone: String,
address: String,
description: String,
created_at: { type: Date, default: Date.now }
})
This will ensure that data with string value will be stored in the database.
Next, you will create a TypeScript interface which will be used for type-checking and to determine the type of values that will be received by the application. To set it up, create a new folder named interfaces
within the ./src/customer
folder. After that, create a new file within the newly created folder and name it customer.interface.ts
. Paste the following code in it :
// ./src/customer/interfaces/customer.interface.ts
import { Document } from 'mongoose';
export interface Customer extends Document {
readonly first_name: string;
readonly last_name: string;
readonly email: string;
readonly phone: string;
readonly address: string;
readonly description: string;
readonly created_at: Date;
}
A data transfer object will define how data will be sent on over the network. To do this, create a folder dto
inside ./src/customer
folder and create a new file create-customer.dto.ts
and paste the code below in it:
// ./src/customer/dto/create-customer.dto.ts
export class CreateCustomerDTO {
readonly first_name: string;
readonly last_name: string;
readonly email: string;
readonly phone: string;
readonly address: string;
readonly description: string;
readonly created_at: Date;
}
You are now done with the basic configurations of connecting and interacting with the database
Creating module, controller and service for the applicationA module in Nest.js is identified by the @Module()
decorator and it takes in objects such as controllers
and providers
. Here you will leverage on the nest
command to easily generate a new module for the customer app. This will ensure that the application is properly structured and more organized. Stop the application, if it is currently running with CTRL + C and run the following command
nest generate module customer
This will create a new file named customer.module.ts
within the src/customer
folder and update the root module (i.e app.module.ts
) of the application with the details of the newly created module.
// ./src/customer/customer.module.ts
import { Module } from '@nestjs/common';
@Module({})
export class CustomerModule {}
You will come back to add more content to this module later in this post.
Service also known as provider in Nest.js basically carry out the task of abstracting logic away from controllers. With it in place, a controller will only carry out the functionality of handling HTTP requests from the frontend and delegate the complex tasks to services. Service or provider in Nest.js is identified by adding @Injectable()
decorator on top of them.
Generate a new service using the nest
command by running the following command from the terminal within the project directory:
nest generate service customer
After successfully running the command above, two new files will be created. They are:
customer.service.ts
: this is the main service file with @Injectable()
decoratorcustomer.service.spec.ts
: a file for unit testing. You can ignore this file for now as testing will not be covered in this tutorial.The customer.service.ts
file holds all the logic as regards database interaction for creating and updating every details of a new customer. In a nutshell, the service will receive a request from the controller, communicate this to the database and return an appropriate response afterwards.
Open the newly created customer.service.ts
file and replace the existing code with the following :
// ./src/customer/customer.service.ts
import { Injectable } from '@nestjs/common';
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { Customer } from './interfaces/customer.interface';
import { CreateCustomerDTO } from './dto/create-customer.dto';
@Injectable()
export class CustomerService {
constructor(@InjectModel('Customer') private readonly customerModel: Model<Customer>) { }
// fetch all customers
async getAllCustomer(): Promise<Customer[]> {
const customers = await this.customerModel.find().exec();
return customers;
}
// Get a single customer
async getCustomer(customerID): Promise<Customer> {
const customer = await this.customerModel.findById(customerID).exec();
return customer;
}
// post a single customer
async addCustomer(createCustomerDTO: CreateCustomerDTO): Promise<Customer> {
const newCustomer = await this.customerModel(createCustomerDTO);
return newCustomer.save();
}
// Edit customer details
async updateCustomer(customerID, createCustomerDTO: CreateCustomerDTO): Promise<Customer> {
const updatedCustomer = await this.customerModel
.findByIdAndUpdate(customerID, createCustomerDTO, { new: true });
return updatedCustomer;
}
// Delete a customer
async deleteCustomer(customerID): Promise<any> {
const deletedCustomer = await this.customerModel.findByIdAndRemove(customerID);
return deletedCustomer;
}
}
Here, you imported the required module from @nestjs/common
, mongoose
and @nestjs/mongoose
. In addition, you also imported the interface created earlier named Customer and a data transfer object CreateCustomerDTO
.
In order to be able to seamlessly carry out several database related activities such as, creating a customer, retrieving the list of customers or just a single customer, you used the @InjectModel
method to inject the Customer
model into the CustomerService class.
Next, you created the following methods:
getAllCustomer()
: to retrieve and return the list of customers from the databasegetCustomer()
: it takes customerID
as a parameter and based on that, it will search and return the details of a user identified by that ID
.addCustomer()
: used to add a new customer to the databaseupdateCustomer()
: this method also takes the ID
of a customer as an argument and will be used to edit and update the details of such customer in the database.deleteCustomer():
this will be used to delete the details of a particular customer completely from the database.Handling each route within the application is one of the major responsibility of controllers
in Nest.js. Similar to most JavaScript server-side framework for the web, several endpoints will be created and any requests sent to such endpoint from the client side will be mapped to a specific method within the controller and an appropriate response will be returned.
Again, you will use the nest
command to generate the controller for this application. To achieve that, run the following command:
nest generate controller customer
This command will also generate two new files within the src/customer
, they are , customer.controller.spec.ts
and customer.controller.ts
files respectively. The customer.controller.ts
file is the actual controller file and the second one should be ignored for now. Controllers in Nest.js are TypeScript files decorated with @Controller
metadata.
Now open the controller and replace the content with the following code that contains methods to create a new customer, retrieve the details of a particular customer and fetch the list of all customers from the database:
// ./src/customer/customer.controller.ts
import { Controller, Get, Res, HttpStatus, Post, Body, Put, Query, NotFoundException, Delete, Param } from '@nestjs/common';
import { CustomerService } from './customer.service';
import { CreateCustomerDTO } from './dto/create-customer.dto';
@Controller('customer')
export class CustomerController {
constructor(private customerService: CustomerService) { }
// add a customer
@Post('/create')
async addCustomer(@Res() res, @Body() createCustomerDTO: CreateCustomerDTO) {
const customer = await this.customerService.addCustomer(createCustomerDTO);
return res.status(HttpStatus.OK).json({
message: "Customer has been created successfully",
customer
})
}
// Retrieve customers list
@Get('customers')
async getAllCustomer(@Res() res) {
const customers = await this.customerService.getAllCustomer();
return res.status(HttpStatus.OK).json(customers);
}
// Fetch a particular customer using ID
@Get('customer/:customerID')
async getCustomer(@Res() res, @Param('customerID') customerID) {
const customer = await this.customerService.getCustomer(customerID);
if (!customer) throw new NotFoundException('Customer does not exist!');
return res.status(HttpStatus.OK).json(customer);
}
}
In order to interact with the database, the CustomerService
was injected into the controller via the class constructor()
. The addCustomer()
and getAllCustomer()
methods will be used to add a new customer’s details and retrieve the list of customers while the getCustomer()
receives the customerID
as a query parameter and throw an exception if the customer does not exist in the database.
Next, you need to be able to update and delete the details of a customer where and when necessary. For this you will add two more methods to the CustomerController
class. Open the file again and add this:
// ./src/customer/customer.controller.ts
...
@Controller('customer')
export class CustomerController {
constructor(private customerService: CustomerService) { }
...
// Update a customer's details
@Put('/update')
async updateCustomer(@Res() res, @Query('customerID') customerID, @Body() createCustomerDTO: CreateCustomerDTO) {
const customer = await this.customerService.updateCustomer(customerID, createCustomerDTO);
if (!customer) throw new NotFoundException('Customer does not exist!');
return res.status(HttpStatus.OK).json({
message: 'Customer has been successfully updated',
customer
});
}
// Delete a customer
@Delete('/delete')
async deleteCustomer(@Res() res, @Query('customerID') customerID) {
const customer = await this.customerService.deleteCustomer(customerID);
if (!customer) throw new NotFoundException('Customer does not exist');
return res.status(HttpStatus.OK).json({
message: 'Customer has been deleted',
customer
})
}
}
Update the customer module
To keep things properly organised go back to the customer.module.ts
and set up the Customer
model. Update the content with the following:
// ./src/customer/customer.module.ts
import { Module } from '@nestjs/common';
import { CustomerController } from './customer.controller';
import { CustomerService } from './customer.service';
import { MongooseModule } from '@nestjs/mongoose';
import { CustomerSchema } from './schemas/customer.schema';
@Module({
imports: [
MongooseModule.forFeature([{ name: 'Customer', schema: CustomerSchema }])
],
controllers: [CustomerController],
providers: [CustomerService]
})
export class CustomerModule { }
Enable CORS
By default, it is forbidden for two separate application on different ports to interact or share resources with each other unless it is otherwise allowed by one of them, which is often the server-side. In order to allow request from the client side that will be built with Vue.js, you will need to enable CORS (Cross-Origin Resource Sharing).
To do that in Nest.js, you only need to add app.enableCors()
to the main.ts
file as shown below:
// ./src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors(); // add this line
await app.listen(3000);
}
bootstrap();
With this, you have just completed the backend part of the application and can now proceed to build the frontend with Vue.js
Creating the frontend app with Vue.jsThe team at Vue.js already created an awesome tool named Vue CLI. It is a standard tool that allows you to quickly generate and install a new Vue.js project with ease. You will use that here to create the frontend part of the customer app, but first you need to install Vue CLI globally on your machine.
Open a new terminal and run:
npm install -g @vue/cli
Once the installation process is complete, you can now use the vue create
command to craft a new Vue.js project. Run the following command to do that for this project:
vue create customer-list-app-frontend
Immediately after you hit return, you will be prompted to pick a preset. You can choose manually select features:
Next, check the features you will need for this project by using the up and down arrow key on your keyboard to navigate through the list of features. Press the spacebar to select a feature from the list. Select Babel, Router and Linter / Formatter as shown here:
Hitting return here will show you another list of options For other instructions, type y
to use history mode for a router, this will ensure that history mode is enabled within the router file that will automatically be generated for this project.
Next, select ESLint with error prevention only
in order to pick a linter / formatter config. After that, select Lint on save
for additional lint features and save your configuration in a dedicated config file for future projects. Type a name for your preset, I named mine vuescotch
:
This will create a Vue.js application in a directory named customer-list-app-frontend
and install all its required dependencies.
You can now change directory into the newly created project and start the application with:
// change directory
cd customer-list-app-frontend
// run the application
npm run server
You can now view the application on http://localhost:8080
Install axiosAxios, a promised based HTTP client for browser will be used here to perform HTTP requests from different component within the application. Stop the frontend application from running by hitting CTRL + C
from the terminal and run the following command afterwards:
npm install axios --save
Once the installation process is completed, open the customer-list-app-frontend
within a code editor and create a new file named helper.js
within the src
folder. Open the newly created file and paste the following content in it:
// ./src/helper.js
export const server = {
baseURL: 'http://localhost:3000'
}
What you have done here is to define the baseURL
for the backend project built with Nest.js. This is just to ensure that you don’t have to start declaring this URL within several Vue.js components that you will create in the next section.
Vue.js favours building and structuring applications by creating reusable components to give it a proper structure. Vue.js components contains three different sections, which are
<template></template>
<script></script>
<style></style>
.You will start by creating a component within the application for a user to create a customer. This component will contain a form with few input fields required to accepts details of a customer and once the form is submitted, the details from the input fields will be posted to the server. To achieve this, create a new folder named customer
within the ./src/components
folder. This newly created folder will house all the components for this application. Next, create another file within the customer
folder and name it Create.vue
. Open this new file and add the following:
// ./src/components/customer/Create.vue
<template>
<div>
<div class="col-md-12 form-wrapper">
<h2> Create Customer </h2>
<form id="create-post-form" @submit.prevent="createCustomer">
<div class="form-group col-md-12">
<label for="title"> First Name </label>
<input type="text" id="first_name" v-model="first_name" name="title" class="form-control" placeholder="Enter firstname">
</div>
<div class="form-group col-md-12">
<label for="title"> Last Name </label>
<input type="text" id="last_name" v-model="last_name" name="title" class="form-control" placeholder="Enter Last name">
</div>
<div class="form-group col-md-12">
<label for="title"> Email </label>
<input type="text" id="email" v-model="email" name="title" class="form-control" placeholder="Enter email">
</div>
<div class="form-group col-md-12">
<label for="title"> Phone </label>
<input type="text" id="phone_number" v-model="phone" name="title" class="form-control" placeholder="Enter Phone number">
</div>
<div class="form-group col-md-12">
<label for="title"> Address </label>
<input type="text" id="address" v-model="address" name="title" class="form-control" placeholder="Enter Address">
</div>
<div class="form-group col-md-12">
<label for="description"> Description </label>
<input type="text" id="description" v-model="description" name="description" class="form-control" placeholder="Enter Description">
</div>
<div class="form-group col-md-4 pull-right">
<button class="btn btn-success" type="submit"> Create Customer </button>
</div> </form>
</div>
</div>
</template>
This is the <template></template>
section that contains the details of the input fields. Next, paste the following code just after the end of the </template>
tag:
// ./src/components/customer/Create.vue
...
<script>
import axios from "axios";
import { server } from "../../helper";
import router from "../../router";
export default {
data() {
return {
first_name: "",
last_name: "",
email: "",
phone: "",
address: "",
description: ""
};
},
methods: {
createCustomer() {
let customerData = {
first_name: this.first_name,
last_name: this.last_name,
email: this.email,
phone: this.phone,
address: this.address,
description: this.description
};
this.__submitToServer(customerData);
},
__submitToServer(data) {
axios.post(`${server.baseURL}/customer/create`, data).then(data => {
router.push({ name: "home" });
});
}
}
};
</script>
Here, you created a method createCustomer()
to receive the details of a customer via the input fields and used axios to post the data to the server.
Similar to the CreateCustomer
component, you need to create another component to edit customer’s details. Navigate to ./src/components/customer
and create a new file named Edit.vue
. Paste the following code in it:
// ./src/components/customer/Edit.vue
<template>
<div>
<h4 class="text-center mt-20">
<small>
<button class="btn btn-success" v-on:click="navigate()"> View All Customers </button>
</small>
</h4>
<div class="col-md-12 form-wrapper">
<h2> Edit Customer </h2>
<form id="create-post-form" @submit.prevent="editCustomer">
<div class="form-group col-md-12">
<label for="title"> First Name </label>
<input type="text" id="first_name" v-model="customer.first_name" name="title" class="form-control" placeholder="Enter firstname">
</div>
<div class="form-group col-md-12">
<label for="title"> Last Name </label>
<input type="text" id="last_name" v-model="customer.last_name" name="title" class="form-control" placeholder="Enter Last name">
</div>
<div class="form-group col-md-12">
<label for="title"> Email </label>
<input type="text" id="email" v-model="customer.email" name="title" class="form-control" placeholder="Enter email">
</div>
<div class="form-group col-md-12">
<label for="title"> Phone </label>
<input type="text" id="phone_number" v-model="customer.phone" name="title" class="form-control" placeholder="Enter Phone number">
</div>
<div class="form-group col-md-12">
<label for="title"> Address </label>
<input type="text" id="address" v-model="customer.address" name="title" class="form-control" placeholder="Enter Address">
</div>
<div class="form-group col-md-12">
<label for="description"> Description </label>
<input type="text" id="description" v-model="customer.description" name="description" class="form-control" placeholder="Enter Description">
</div>
<div class="form-group col-md-4 pull-right">
<button class="btn btn-success" type="submit"> Edit Customer </button>
</div> </form>
</div>
</div>
</template>
<script>
import { server } from "../../helper";
import axios from "axios";
import router from "../../router";
export default {
data() {
return {
id: 0,
customer: {}
};
},
created() {
this.id = this.$route.params.id;
this.getCustomer();
},
methods: {
editCustomer() {
let customerData = {
first_name: this.customer.first_name,
last_name: this.customer.last_name,
email: this.customer.email,
phone: this.customer.phone,
address: this.customer.address,
description: this.customer.description
};
axios
.put(
`${server.baseURL}/customer/update?customerID=${this.id}`,
customerData
)
.then(data => {
router.push({ name: "home" });
});
},
getCustomer() {
axios
.get(`${server.baseURL}/customer/customer/${this.id}`)
.then(data => (this.customer = data.data));
},
navigate() {
router.go(-1);
}
}
};
</script>
The route parameter was used here to fetch the details of a customer from the database and populated the inputs fields with it. As a user of the application, you can now edit the details and submit back to the server.
The editCustomer()
method within the <script></script>
was used to send a PUT HTTP request to the server.
Finally, to fetch and show the complete list of customers from the server, you will create a new component. Navigate to the views
folder within the src
folder, you should see a Home.vue
file, if otherwise, create it and paste this code in it:
// ./src/views/Home.vue
<template>
<div class="container-fluid">
<div class="text-center">
<h1>Nest Customer List App Tutorial</h1>
<p> Built with Nest.js, Vue.js and MongoDB</p>
<div v-if="customers.length === 0">
<h2> No customer found at the moment </h2>
</div>
</div>
<div class="">
<table class="table table-bordered">
<thead class="thead-dark">
<tr>
<th scope="col">Firstname</th>
<th scope="col">Lastname</th>
<th scope="col">Email</th>
<th scope="col">Phone</th>
<th scope="col">Address</th>
<th scope="col">Description</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="customer in customers" :key="customer._id">
<td>{{ customer.first_name }}</td>
<td>{{ customer.last_name }}</td>
<td>{{ customer.email }}</td>
<td>{{ customer.phone }}</td>
<td>{{ customer.address }}</td>
<td>{{ customer.description }}</td>
<td>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group" style="margin-bottom: 20px;">
<router-link :to="{name: 'Edit', params: {id: customer._id}}" class="btn btn-sm btn-outline-secondary">Edit Customer </router-link>
<button class="btn btn-sm btn-outline-secondary" v-on:click="deleteCustomer(customer._id)">Delete Customer</button>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import { server } from "../helper";
import axios from "axios";
export default {
data() {
return {
customers: []
};
},
created() {
this.fetchCustomers();
},
methods: {
fetchCustomers() {
axios
.get(`${server.baseURL}/customer/customers`)
.then(data => (this.customers = data.data));
},
deleteCustomer(id) {
axios
.delete(`${server.baseURL}/customer/delete?customerID=${id}`)
.then(data => {
console.log(data);
window.location.reload();
});
}
}
};
</script>
Within <template>
section, you created an HTML table to display all customers details and used the <router-link>
to create a link for editing and to a view a single customer by passing the customer._id
as a query parameter. And finally, within the <script>
section of this file, you created a method named fetchCustomers()
to fetch all customers from the database and updated the page with the data returned from the server.
Open the AppComponent
of the application and update it with the links to both Home
and Create
component by using the content below:
// ./src/App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/create">Create</router-link>
</div>
<router-view/>
</div>
</template>
<style>
...
.form-wrapper {
width: 500px;
margin: 0 auto;
}
</style>
Also included is a <style></style>
section to include styling for the forms.
Navigate to the index.html
file within the public folder and include the CDN file for bootstrap as shown below. This is just to give the page some default style:
<!DOCTYPE html>
<html lang="en">
<head>
...
<!-- Add this line -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<title>customer-list-app-frontend</title>
</head>
<body>
...
</body>
</html>
Setting up routing
Finally, configure the router file within ./src/router.js
to include the link to all the required reusable components created so far by updating its content as shown here:
// ./src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import HomeComponent from '@/views/Home';
import EditComponent from '@/components/customer/Edit';
import CreateComponent from '@/components/customer/Create';
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{ path: '/', redirect: { name: 'home' } },
{ path: '/home', name: 'home', component: HomeComponent },
{ path: '/create', name: 'Create', component: CreateComponent },
{ path: '/edit/:id', name: 'Edit', component: EditComponent },
]
});
Test the application
You can now proceed to test the application by running npm run serve
to start it and navigate to http://localhost:8080 to view it:
Ensure that the backend server is running at this moment, if otherwise, navigate to the backend application from a different terminal and run:
npm run start
Lastly, also ensure that the MongoDB instance is running as well. Use sudo mongod
from another terminal on your local system, if it is not running at the moment.
In this tutorial, you have created a simple customer list management application by using Nest.js and Vue.js. Here, you used Nest.js to build a RESTful backend API and then leveraged on Vue.js to craft a client that consumes the API.
This has given you an overview of how to structure Nest.js application and integrate it with a MongoDB database.
I hope you found this tutorial helpful. Don’t hesitate to explore the source code of both application by checking it out here on GitHub.
Originally published by Olususi Kayode Oluyemi at https://scotch.io