How To Build a Blog with Nest.js, MongoDB, and Vue.js

How To Build a Blog with Nest.js, MongoDB, and Vue.js

Firstly, you'll concentrate on the RESTful back-end API built with Nest.js. You'll then focus on the frontend, which you will build with Vue.js.

Firstly, you'll concentrate on the RESTful back-end API built with Nest.js. You'll then focus on the frontend, which you will build with Vue.js.

Introduction

Nest.js is a scalable, server-side JavaScript framework built with TypeScript that still preserves compatibility with JavaScript, which makes it an effective tool for building efficient and reliable back-end applications. It has a modular architecture that provides a mature, structural design pattern to the Node.js development world.

Vue.js is a front-end JavaScript framework for building user interfaces. It has a simple, yet very powerful API along with great performance. Vue.js is capable of powering the front-end layer and logic of any web application irrespective of the size. The ease of integrating it with other libraries or existing projects makes it a perfect choice for most modern web applications.

In this tutorial, you’ll build a Nest.js application to get yourself familiar with its building blocks as well as the fundamental principles of building modern web applications. You’ll approach this project by separating the application into two different sections: the frontend and the backend. Firstly, you’ll concentrate on the RESTful back-end API built with Nest.js. You’ll then focus on the frontend, which you will build with Vue.js. Both applications will run on different ports and will function as separate domains.

You’ll build a blog application with which users can create and save a new post, view the saved posts on the homepage, and carry out other processes such as editing and deleting posts. Furthermore, you’ll connect your application and persist its data with MongoDB, which is a schema-less NoSQL database that can receive and store JSON documents. This tutorial focuses on building your application in a development environment. For a production environment, you should also consider user authentication for your application.

Prerequisites

To complete this tutorial, you will need:

Note: This tutorial uses a macOS machine for development. If you’re using another operating system, you may need to use sudo for npm commands throughout the tutorial.

Step 1 — Installing Nest.js and Other Dependencies

In this section, you will get started with Nest.js by installing the application and its required dependencies on your local machine. You can easily install Nest.js by either using the CLI that Nest.js provides, or, by installing the starter project from GitHub. For the purpose of this tutorial, you’ll use the CLI to set up the application. To begin, run the following command from the terminal to have it installed globally on your machine:

npm i -g @nestjs/cli


You will see output similar to the following:

[email protected]/[email protected]
added 220 packages from 163 contributors in 49.104s


To confirm your installation of the Nest CLI, run this command from your terminal:

nest --version


You’ll see output showing the current version installed on your machine:

Output5.8.0


You’ll make use of the nest command to manage your project and use it to generate relevant files — like the controller, modules, and providers.

To begin the project for this tutorial, use the nest command to craft a new Nest.js project named blog-backend by running the following command from your terminal:

nest new blog-backend


Immediately after running the command, nest will prompt you to provide some basic information like the descriptionversion, and author. Go ahead and provide the appropriate details. Hit ENTER on your computer to proceed after responding to each prompt.

Next, you’ll choose a package manager. For the purpose of this tutorial, select npm and hit ENTER to start installing Nest.js.

This will generate a new Nest.js project in a blog-backend folder within your local development folder.

Next, navigate to the new project’s folder from your terminal:

cd blog-backend


Run the following command to install other server dependencies:

npm install --save @nestjs/mongoose mongoose


You’ve installed @nestjs/mongoose, which is a Nest.js dedicated package for an object modelling tool for MongoDB, and mongoose, which is a package for Mongoose.

Now you’ll start the application using the following command:

npm run start


Now, if you navigate to [http://localhost:3000](http://localhost:3000 "http://localhost:3000") from your favorite browser, you will see your application running.

You’ve successfully generated the project by leveraging the availability of the Nest CLI command. Afterward, you proceeded to run the application and accessed it on the default port 3000 on your local machine. In the next section, you’ll take the application further by setting up the configuration for the database connection.

Step 2 — Configuring and Connecting with the Database

In this step, you’ll configure and integrate MongoDB into your Nest.js application. You’ll use MongoDB to store data for your application. MongoDB stores its data in documents as field : value pairs. To access this data structure, you’ll use Mongoose, which is an object document modeling (ODM) that allows you to define schemas representing the types of data that a MongoDB database stores.

To start MongoDB, open a separate terminal window so that the application can keep running, and then execute the following command:

sudo mongod


This will start the MongoDB service and run the database in the background of your machine.

Open the project blog-backend in your text editor and navigate to ./src/app.module.ts. You can set up a connection to the database by including the installed MongooseModulewithin the root ApplicationModule. To achieve this, update the content in app.module.ts with the following highlighted lines:

~/blog-backend/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/nest-blog', { useNewUrlParser: true }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }


In this file, you use the forRoot() method to supply the connection to the database. Save and close the file when you are finished editing.

With this in place, you have set up the database connection by using the Mongoose module for MongoDB. In the next section, you will create a database schema using the Mongoose library, a TypeScript interface, and a data transfer object (DTO) schema.

Step 3 — Creating a Database Schema, Interfaces, and DTO

In this step, you will create a schemainterface, and a data transfer object for your database using Mongoose. Mongoose helps to manage relationships between data and provides schema validation for data types. To help define the structure and datatype of the data in your application’s database, you’ll create files that determine the following:

  • database schema: This is an organization of data as a blueprint for defining the structure and the types of data that the database needs to store.

  • interfaces: TypeScript interfaces are used for type-checking. It can be used to define the types of data that should be passed for an application.

  • data transfer object: This is an object that defines how data will be sent over the network and carries the data between processes.

To begin, go back to your terminal where the application is currently running and stop the process with CTRL + C, then navigate to the ./src/ folder:

cd ./src/


Then, create a directory named blog, and a schemas folder within that:

mkdir -p blog/schemas


In the schemas folder, create a new file called blog.schema.ts and open it using your text editor. Then, add the following content:

~/blog-backend/src/blog/schemas/blog.schema.ts

import * as mongoose from 'mongoose';

export const BlogSchema = new mongoose.Schema({
    title: String,
    description: String,
    body: String,
    author: String,
    date_posted: String
})


Here, you have used Mongoose to define the type of data that you will store in the database. You’ve specified that all the fields will store and only accept string values. Save and close the file when you are finished editing.

Now, with the database schema determined, you can move on to creating the interfaces.

To begin, navigate back into the blog folder:

cd ~/blog-backend/src/blog/


Create a new folder named interfaces and move into it:

mkdir interfaces


In the interfaces folder, create a new file called post.interface.ts and open it using your text editor. Add the following content to define the types of data for a Post:

~/blog-backend/src/blog/interfaces/post.interface.ts

import { Document } from 'mongoose';

export interface Post extends Document {
    readonly title: string;
    readonly description: string;
    readonly body: string;
    readonly author: string;
    readonly date_posted: string
}


In this file, you have successfully defined the types of data for a Post type as string values. Save and exit the file.

Since your application will carry out the functionality of posting data to the database, you will create a data transfer object that will define how data will be sent over the network.

To achieve this, create a folder dto inside the ./src/blog folder. Within the newly created folder, create another file named create-post.dto.ts

Navigate back into the blog folder:

cd ~/blog-backend/src/blog/


Then create a folder named dto and move into it:

mkdir dto


In the dto folder, create a new file called create-post.dto.ts and open it using your text editor to add the following content:

~/blog-backend/src/blog/dto/create-post.dto.ts

export class CreatePostDTO {
    readonly title: string;
    readonly description: string;
    readonly body: string;
    readonly author: string;
    readonly date_posted: string
}


You’ve marked each of the individual properties in the CreatePostDTO class to have a data type of string and as readonly to avoid unnecessary mutation. Save and exit the file when you are finished editing.

In this step, you have created a database schema for the database, an interface, and a data transfer object for the data your database will store. Next, you’ll generate a module, controller, and service for your blog.

Step 4 — Creating the Module, Controller, and Service for the Blog

In this step, you’re going to improve on the existing structure of the application by creating a module for your blog. This module will organize the file structure of your application. Next, you’ll create a controller to handle routes and process HTTP requests from the client. To wrap things up, you’ll set up a service to handle all the business logic that is too complex for the controller of the application to process.

Generating a Module

Similarly to the Angular front-end web framework, Nest.js uses a modular syntax. Nest.js applications have a modular design; it comes installed with a single root module, which is often sufficient for a small application. But when an application starts to grow, Nest.js recommends a multiple-module organization, splitting the code into related features.

module in Nest.js is identified by the @Module() decorator and takes in an object with properties such as controllers and providers. Each of these properties takes an array of controllers and providersrespectively.

You will generate a new module for this blog application in order to keep the structure more organized. To begin, still in the ~/blog-backend folder, execute the following command:

nest generate module blog


You will see output similar to the following:

OutputCREATE /src/blog/blog.module.ts

UPDATE /src/app.module.ts


The command generated a new module named blog.module.ts for the application and imported the newly created module into the root module for the application. This will allow Nest.js to be aware of another module besides the root module.

In this file, you will see the following code:

~/blog-backend/src/blog/blog.module.ts

import { Module } from '@nestjs/common';

@Module({})
export class BlogModule {}


You will update this BlogModule with the required properties later in the tutorial. Save and exit the file.

Generating a Service

service, which can also be called a provider in Nest.js, was designed to remove logic from controllers, which are meant to only handle HTTP requests and redirect more complex tasks to services. Services are plain JavaScript classes with an @Injectable() decorator on top of them. To generate a new service, run the following command from the terminal while you are still within the project directory:

nest generate service blog


You will see output similar to the following:

Output  CREATE /src/blog/blog.service.spec.ts (445 bytes)

CREATE /src/blog/blog.service.ts (88 bytes)

UPDATE /src/blog/blog.module.ts (529 bytes)


The nest command used here has created a blog.service.spec.ts file, which you can use for testing. It has also created a new blog.service.ts file, which will hold all the logic for this application and handle adding and retrieving documents to the MongoDB database. Also, it automatically imported the newly created service and added to blog.module.ts.

The service handles all the logic within the application, is responsible for interacting with the database, and returns the appropriate responses back to the controller. To accomplish this, open the blog.service.ts file in your text editor and replace the contents with the following:

~/blog-backend/src/blog/blog.service.ts

import { Injectable } from '@nestjs/common';
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { Post } from './interfaces/post.interface';
import { CreatePostDTO } from './dto/create-post.dto';

@Injectable()
export class BlogService {

    constructor(@InjectModel('Post') private readonly postModel: Model) { }

    async getPosts(): Promise {
        const posts = await this.postModel.find().exec();
        return posts;
    }

    async getPost(postID): Promise {
        const post = await this.postModel
            .findById(postID)
            .exec();
        return post;
    }

    async addPost(createPostDTO: CreatePostDTO): Promise {
        const newPost = await this.postModel(createPostDTO);
        return newPost.save();
    }

    async editPost(postID, createPostDTO: CreatePostDTO): Promise {
        const editedPost = await this.postModel
            .findByIdAndUpdate(postID, createPostDTO, { new: true });
        return editedPost;
    }

    async deletePost(postID): Promise {
        const deletedPost = await this.postModel
            .findByIdAndRemove(postID);
        return deletedPost;
    }

}


In this file, you first imported the required module from @nestjs/commonmongoose, and @nestjs/mongoose. You also imported an interface named Post and a data transfer object CreatePostDTO.

In the constructor, you added @InjectModel(``'``Post``'``), which will inject the Post model into this BlogService class. You will now be able to use this injected model to retrieve all posts, fetch a single post, and carry out other database-related activities.

Next, you created the following methods:

  • getPosts(): to fetch all posts from the database.
  • getPost(): to retrieve a single post from the database.
  • addPost(): to add a new post.
  • editPost(): to update a single post.
  • deletePost(): to delete a particular post.

Save and exit the file when you are finished.

You have finished setting up and creating several methods that will handle proper interaction with the MongoDB database from the back-end API. Now, you will create the required routes that will handle HTTP calls from a front-end client.

Generating a Controller

In Nest. js, controllers are responsible for handling any incoming requests from the client side of an application and returning the appropriate response. Similarly to most other web frameworks, it is important for the application to listen for a request and respond to it.

To cater to all the HTTP requests for your blog application, you will leverage the nest command to generate a new controller file. Ensure that you are still in the project directory, blog-backend, and run the following command:

nest generate controller blog


You will see output similar to:

OutputCREATE /src/blog/blog.controller.spec.ts (474 bytes)

CREATE /src/blog/blog.controller.ts (97 bytes)

UPDATE /src/blog/blog.module.ts (483 bytes)


The output indicates that this command created two new files within the src/blog directory. They are blog.controller.spec.ts and blog.controller.ts. The former is a file that you can use to write automated testing for the newly created controller. The latter is the controller file itself. Controllers in Nest.js are TypeScript files decorated with @Controller metadata. The command also imported the newly created controller and added to the blog module.

Next, open the blog.controller.ts file with your text editor and update it with the following content:

~/blog-backend/src/blog/blog.controller.ts

import { Controller, Get, Res, HttpStatus, Param, NotFoundException, Post, Body, Query, Put, Delete } from '@nestjs/common';
import { BlogService } from './blog.service';
import { CreatePostDTO } from './dto/create-post.dto';
import { ValidateObjectId } from '../shared/pipes/validate-object-id.pipes';

@Controller('blog')
export class BlogController {

    constructor(private blogService: BlogService) { }

    @Get('posts')
    async getPosts(@Res() res) {
        const posts = await this.blogService.getPosts();
        return res.status(HttpStatus.OK).json(posts);
    }

    @Get('post/:postID')
    async getPost(@Res() res, @Param('postID', new ValidateObjectId()) postID) {
        const post = await this.blogService.getPost(postID);
        if (!post) throw new NotFoundException('Post does not exist!');
        return res.status(HttpStatus.OK).json(post);

    }

    @Post('/post')
    async addPost(@Res() res, @Body() createPostDTO: CreatePostDTO) {
        const newPost = await this.blogService.addPost(createPostDTO);
        return res.status(HttpStatus.OK).json({
            message: "Post has been submitted successfully!",
            post: newPost
        })
    }
}


In this file, you first imported the necessary modules to handle HTTP requests from @nestjs/common module. Then, you imported three new modules which are: BlogServiceCreatePostDTO, and ValidateObjectId. After that, you injected the BlogService into the controller via a constructor in order to gain access and make use of the functions that are already defined within the BlogService file. This is a pattern regarded as dependency injection used in Nest.js to increase efficiency and enhance the modularity of the application.

Finally, you created the following asynchronous methods:

  • getPosts(): This method will carry out the functionality of receiving an HTTP GET request from the client to fetch all posts from the database and then return the appropriate response. It is decorated with a @Get('posts').

  • getPost(): This takes a postID as a parameter and fetches a single post from the database. In addition to the postID parameter passed to this method, you realized the addition of an extra method named ValidateObjectId(). This method implements the PipeTransform interface from Nest.js. Its purpose is to validate and ensure that the postID parameter can be found in the database. You will define this method in the next section.

  • addPost(): This method will handle a POST HTTP request to add a new post to the database.

To be able to edit and delete a particular post, you will need to add two more methods to the blog.controller.tsfile. To do that, include the following editPost() and deletePost() methods directly after the addPost() method you previously added to blog.controller.ts:

~/blog-backend/src/blog/blog.controller.ts

...
@Controller('blog')
export class BlogController {
    ...
    @Put('/edit')
    async editPost(
        @Res() res,
        @Query('postID', new ValidateObjectId()) postID,
        @Body() createPostDTO: CreatePostDTO
    ) {
        const editedPost = await this.blogService.editPost(postID, createPostDTO);
        if (!editedPost) throw new NotFoundException('Post does not exist!');
        return res.status(HttpStatus.OK).json({
            message: 'Post has been successfully updated',
            post: editedPost
        })
    }

    @Delete('/delete')
    async deletePost(@Res() res, @Query('postID', new ValidateObjectId()) postID) {
        const deletedPost = await this.blogService.deletePost(postID);
        if (!deletedPost) throw new NotFoundException('Post does not exist!');
        return res.status(HttpStatus.OK).json({
            message: 'Post has been deleted!',
            post: deletedPost
        })
    }
}


Here you have added:

  • editPost(): This method accepts a query parameter of postID and will carry out the functionality of updating a single post. It also made use of the ValidateObjectId method to provide proper validation for the post that you need to edit.

  • deletePost(): This method will accept a query parameter of postID and will delete a particular post from the database.

Similarly to the BlogController, each of the asynchronous methods you have defined here has a metadata decorator and takes in a prefix that Nest.js uses as a routing mechanism. It controls which controller receives which requests and points to the methods that should process the request and return a response respectively.

For example, the BlogController that you have created in this section has a prefix of blog and a method named getPosts() that takes in a prefix of posts. This means that any GET request sent to an endpoint of blog/posts(http:localhost:3000/blog/posts) will be handled by the getPosts()method. This example is similar to how other methods will handle HTTP requests.

Save and exit the file.

For the complete blog.controller.ts file, visit the DO Community repository for this application.

In this section, you have created a module to keep the application more organized. You also created a service to handle the business logic for the application by interacting with the database and returning the appropriate response. Finally, you generated a controller and created the required methods to handle HTTP requests such as GETPOSTPUT, and DELETE from the client side. In the next step, you’ll complete your back-end setup.

Step 5 — Creating an Extra Validation for Mongoose

You can identify each post in your blog application by a unique ID, also known as PostID. This means that fetching a post will require you to pass this ID as a query parameter. To validate this postID parameter and ensure that the post is available in the database, you need to create a reusable function that can be initialized from any method within the BlogController.

To configure this, navigate to the ./src/blog folder:

cd ./src/blog/


Then, create a new folder named shared:

mkdir -p shared/pipes


In the pipes folder, using your text editor, create a new file called validate-object-id.pipes.ts and open it. Add the following content to define the accepted postID data:

~/blog-backend/src/blog/shared/pipes/validate-object-id.pipes.ts

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import * as mongoose from 'mongoose';

@Injectable()
export class ValidateObjectId implements PipeTransform {
    async transform(value: string, metadata: ArgumentMetadata) {
        const isValid = mongoose.Types.ObjectId.isValid(value);
        if (!isValid) throw new BadRequestException('Invalid ID!');
        return value;
    }
}


The ValidateObjectId() class implements the PipeTransform method from the @nestjs/common module. It has a single method named transform() that takes in value as a parameter — postID in this case. With the method above, any HTTP request from the frontend of this application with a postID that can’t be found in the database will be regarded as invalid. Save and close the file.

After creating both the service and controller, you need to set up the Post model that is based on the BlogSchema. This configuration could be set up within the root ApplicationModule, but in this instance building the model in BlogModule will maintain your application’s organization. Open the ./src/blog/blog.module.ts and update it with the following highlighted lines:

~/blog-backend/src/blog/blog.module.ts

import { Module } from '@nestjs/common';
import { BlogController } from './blog.controller';
import { BlogService } from './blog.service';
import { MongooseModule } from '@nestjs/mongoose';
import { BlogSchema } from './schemas/blog.schema';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: 'Post', schema: BlogSchema }])
 ],
  controllers: [BlogController],
  providers: [BlogService]
})
export class BlogModule { }


This module uses the MongooseModule.forFeature() method to define which models should be registered in the module. Without this, injecting the PostModel within the BlogService using @injectModel() decorator wouldn’t work. Save and close the file when you have finished adding the content.

In this step, you’ve created the complete backend RESTful API with Nest.js and integrated it with MongoDB. In the next section, you’ll configure the server to allow HTTP requests from another server, because your frontend application will be running on a different port.

Step 6 — Enabling CORS

An HTTP request from one domain to another is often blocked by default, except when specified by the server to allow it. For your front-end application to make a request to the back-end server, you must enable Cross-origin resource sharing (CORS), which is a technique that allows requests for restricted resources on a web page.

In Nest.js to enable CORS, you need to add a single method to your main.ts file. Open this file in your text editor, which is located at ./src/main.ts, and update it with the following highlighted content:

~/blog-backend/src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors();
  await app.listen(3000);
}
bootstrap();


Save and exit the file.

Now that you have completed the back-end setup, you’ll shift your focus to the frontend and use Vue.js to consume the APIs built so far.

Step 7 — Creating the Vue.js Frontend

In this section, you are going to create your front-end application with Vue.js. Vue CLI is a standard tool that allows you to quickly generate and install a new Vue.js project without much hassle.

To begin, you first need to install the Vue CLI globally on your machine. Open another terminal, and instead of working from the blog-backend folder, navigate to your local project’s development folder and run:

npm install -g @vue/cli


Once the installation process is complete, you’ll make use of the vue command to create a new Vue.js project:

vue create blog-frontend


You’ll see a short prompt after you’ve entered this command. Choose the manually select features option, and then select the features you’ll need for this project by pressing SPACE on your computer to highlight multiple features. You’ll select BabelRouter, and Linter / Formatter.

For the next instructions, type y to use history mode for a router; this will ensure that history mode is enabled within the router file, which will automatically generate for this project. In addition, select ESLint with error prevention only to pick a linter/formatter configuration. Next, select Lint on save for additional Lint features. Then select to save your configuration in a dedicated config file for future projects. Type a name for your preset, like vueconfig.

Vue.js will then start creating the application and all its required dependencies in a directory named blog-frontend.

Once the installation process is complete, navigate inside the Vue.js application:

cd blog-frontend


Then, start the development server with:

npm run serve


Your application will be running on [http://localhost:8080](http://localhost:8080 "http://localhost:8080").

Since you’ll be performing HTTP requests within this application, you’ll need to install Axios, which is a promise-based HTTP client for the browser. You’ll use Axios here to perform HTTP requests from the different components within the application. Stop the front-end application by hitting CTRL + C from the terminal on your computer and then run the following command:

npm install axios --save


Your front-end application will be making an API call to the back-end API on a particular domain from different components within the application. In order to ensure proper structure for this application, you can create a helperfile and define the server baseURL.

To begin, from you terminal still within blog-frontend, navigate to the ./src/folder:

cd ./src/


Create another folder named utils:

mkdir utils


In the utils folder, using your text editor, create a new file called helper.js and open it. Add the following content to define the baseURL for the back-end Nest.js project:

~blog-frontend/src/utils/helper.js

export const server = {

baseURL: 'http://localhost:3000'

}


By defining a baseURL, you’ll be able to call it from anywhere within you Vue.js component files. In the event that you need to change the URL, it will be an easier process to update the baseURL in this file rather than across your application.

In this section, you installed the Vue CLI, a tool for creating a new Vue.js application. You used this tool to craft the blog-frontend application. In addition, you ran the application and installed a library named Axios, which you will use whenever there is an HTTP call within the app. Next, you will create components for the application.

Step 8 — Creating Reusable Components

Now you’re going to create reusable components for your application, which is the standard structure for Vue.js applications. The component system in Vue.js makes it possible for developers to build a single, independent unit of an interface that can have its own state, markup, and style. This makes it appropriate for components in Vue.js to be reusable.

Every Vue.js component contains three different sections:

  • <template>: contains the HTML contents
  • <script>: holds all the basic frontend logic and defines the functions
  • <style>: the stylesheet for each separate component

First, you’ll start by creating a component to create a new post. To do that, create a new folder named post within the ./src/components folder, which will house the necessary reusable components for posts. Then using your text editor, inside the newly created post folder, create another file and name it Create.vue. Open the new file and add the following code, which contains the necessary input fields for submitting a post:

~blog-frontend/src/components/post/Create.vue


                            
<template>
   <div>
        <div class="col-md-12 form-wrapper">
          <h2> Create Post </h2>
          <form id="create-post-form" @submit.prevent="createPost">
               <div class="form-group col-md-12">
                <label for="title"> Title </label>
                <input type="text" id="title" v-model="title" name="title" class="form-control" placeholder="Enter title">
               </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-12">
                  <label for="body"> Write Content </label>
                  <textarea id="body" cols="30" rows="5" v-model="body" class="form-control"></textarea>
              </div>
              <div class="form-group col-md-12">
                  <label for="author"> Author </label>
                  <input type="text" id="author" v-model="author" name="author" class="form-control">
              </div>

              <div class="form-group col-md-4 pull-right">
                  <button class="btn btn-success" type="submit"> Create Post </button>
              </div>          
          </form>
        </div>
    </div>
</template>

This is the <template> section of the CreatePost component. It contains the HTML input elements required to create a new post. Each of the input fields has a v-model directive as an input attribute. This is to ensure two-way data bindings on each of the form input to make it easy for Vue.js to obtain the user’s input.

Next, add the <script> section to the same file directly following the preceding content:

<script>
import axios from "axios";
import { server } from "../../utils/helper";
import router from "../../router";
export default {
  data() {
    return {
      title: "",
      description: "",
      body: "",
      author: "",
      date_posted: ""
    };
  },
  created() {
    this.date_posted = new Date().toLocaleDateString();
  },
  methods: {
    createPost() {
      let postData = {
        title: this.title,
        description: this.description,
        body: this.body,
        author: this.author,
        date_posted: this.date_posted
      };
      this.__submitToServer(postData);
    },
    __submitToServer(data) {
      axios.post(`${server.baseURL}/blog/post`, data).then(data => {
        router.push({ name: "home" });
      });
    }
  }
};
</script>
	

Here you’ve added a method named createPost() to create a new post and submit it to the server using Axios. Once a user creates a new post, the application will redirect back to the homepage where users can view the list of created posts.

You will configure vue-router to implement the redirection later in this tutorial.

Save and close the file when you are finished editing. For the complete Create.vue file, visit the DO Community repository for this application.

Now, you need to create another component for editing a particular post. Navigate to ./src/components/post folder and create another file and name it Edit.vue. Add the following code that contains the `````` section to it:

~blog-frontend/src/components/post/Edit.vue

	
<template>
<div>
      <h4 class="text-center mt-20">
       <small>
         <button class="btn btn-success" v-on:click="navigate()"> View All Posts </button>
       </small>
    </h4>
        <div class="col-md-12 form-wrapper">
          <h2> Edit Post </h2>
          <form id="edit-post-form" @submit.prevent="editPost">
            <div class="form-group col-md-12">
                <label for="title"> Title </label>
                <input type="text" id="title" v-model="post.title" name="title" class="form-control" placeholder="Enter title">
            </div>
            <div class="form-group col-md-12">
                <label for="description"> Description </label>
                <input type="text" id="description" v-model="post.description" name="description" class="form-control" placeholder="Enter Description">
            </div>
            <div class="form-group col-md-12">
                <label for="body"> Write Content </label>
                <textarea id="body" cols="30" rows="5" v-model="post.body" class="form-control"></textarea>
            </div>
            <div class="form-group col-md-12">
                <label for="author"> Author </label>
                <input type="text" id="author" v-model="post.author" name="author" class="form-control">
            </div>

            <div class="form-group col-md-4 pull-right">
                <button class="btn btn-success" type="submit"> Edit Post </button>
            </div>
          </form>
        </div>
    </div>
</template>
	
	

This template section holds similar content as the CreatePost() component; the only difference is that it contains the details of the particular post that needs to be edited.

This template section holds similar content as the CreatePost() component; the only difference is that it contains the details of the particular post that needs to be edited.

Next, add the<script> section directly following the </template> section in Edit.vue:

...
<script>
import { server } from "../../utils/helper";
import axios from "axios";
import router from "../../router";
export default {
  data() {
    return {
      id: 0,
      post: {}
    };
  },
  created() {
    this.id = this.$route.params.id;
    this.getPost();
  },
  methods: {
    editPost() {
      let postData = {
        title: this.post.title,
        description: this.post.description,
        body: this.post.body,
        author: this.post.author,
        date_posted: this.post.date_posted
      };

      axios
        .put(`${server.baseURL}/blog/edit?postID=${this.id}`, postData)
        .then(data => {
          router.push({ name: "home" });
        });
    },
    getPost() {
      axios
        .get(`${server.baseURL}/blog/post/${this.id}`)
        .then(data => (this.post = data.data));
    },
    navigate() {
      router.go(-1);
    }
  }
};
</script>	
	

Here, you obtained the route parameter id to identify a particular post. You then created a method named getPost() to retrieve the details of this post from the database and updated the page with it. Finally, you created an editPost() method to submit the edited post back to the back-end server with a PUT HTTP request.

Save and exit the file. For the complete Edit.vue file, visit the DO Community repository for this application.

Now, you’ll create a new component within the ./src/components/post folder and name it Post.vue. This will allow you to view the details of a particular post from the homepage. Add the following content to Post.vue:

~blog-frontend/src/components/post/Post.vue


<template>
    <div class="text-center">
        <div class="col-sm-12">
      <h4 style="margin-top: 30px;"><small><button class="btn btn-success" v-on:click="navigate()"> View All Posts </button></small></h4>
      <hr>
      <h2>{{ post.title }}</h2>
      <h5><span class="glyphicon glyphicon-time"></span> Post by {{post.author}}, {{post.date_posted}}.</h5>
      <p> {{ post.body }} </p>

    </div>
    </div>
</template>            
    

This code renders the details of a post that includes, title, author, and the post body.

Now, directly following </template>, add the following code to the file:

~blog-frontend/src/components/post/Post.vue

...
<script>
import { server } from "../../utils/helper";
import axios from "axios";
import router from "../../router";
export default {
  data() {
    return {
      id: 0,
      post: {}
    };
  },
  created() {
    this.id = this.$route.params.id;
    this.getPost();
  },
  methods: {
    getPost() {
      axios
        .get(`${server.baseURL}/blog/post/${this.id}`)
        .then(data => (this.post = data.data));
    },
    navigate() {
      router.go(-1);
    }
  }
};
</script>

Similar to the <script> section of the edit post component, you obtained the route parameter id and used it to retrieve the details of a particular post.

Save and close the file when you are finished adding the content. For the complete Post.vue file, visit the DO Community repository for this application.

Next, to display all the created posts to users, you will create a new component. If you navigate to the views folder in src/views, you will see a Home.vue component — if this file is not present, use your text editor to create it, add the following code:

~blog-frontend/src/App.vue


<template>
    <div>

      <div class="text-center">
        <h1>Nest Blog Tutorial</h1>
       <p> This is the description of the blog built with Nest.js, Vue.js and MongoDB</p>

       <div v-if="posts.length === 0">
            <h2> No post found at the moment </h2>
        </div>
      </div>

        <div class="row">
           <div class="col-md-4" v-for="post in posts" :key="post._id">
              <div class="card mb-4 shadow-sm">
                <div class="card-body">
                   <h2 class="card-img-top">{{ post.title }}</h2>
                  <p class="card-text">{{ post.body }}</p>
                  <div class="d-flex justify-content-between align-items-center">
                    <div class="btn-group" style="margin-bottom: 20px;">
                      <router-link :to="{name: 'Post', params: {id: post._id}}" class="btn btn-sm btn-outline-secondary">View Post </router-link>
                       <router-link :to="{name: 'Edit', params: {id: post._id}}" class="btn btn-sm btn-outline-secondary">Edit Post </router-link>
                       <button class="btn btn-sm btn-outline-secondary" v-on:click="deletePost(post._id)">Delete Post</button>
                    </div>
                  </div>

                  <div class="card-footer">
                    <small class="text-muted">Posted on: {{ post.date_posted}}</small><br/>
                    <small class="text-muted">by: {{ post.author}}</small>
                  </div>

                </div>
              </div>
            </div>
      </div>
    </div>
</template>

Here, within the <template> section, you used the <router-link> to create a link for editing as well as for viewing a post by passing the post._id as a query parameter. You also used the v-if directive to conditionally render the post for users. If there is no post from the database, a user will only see this text: No post found at the moment.

Save and exit the file. For the complete Home.vue file, visit the DO Community repository for this application.

Now, directly following the </template> section in Home.vue, add the following </script> section:

  • ~blog-frontend/src/views/Home.vue*
...
<script>
// @ is an alias to /src
import { server } from "@/utils/helper";
import axios from "axios";

export default {
  data() {
    return {
      posts: []
    };
  },
  created() {
    this.fetchPosts();
  },
  methods: {
    fetchPosts() {
      axios
        .get(`${server.baseURL}/blog/posts`)
        .then(data => (this.posts = data.data));
    },
    deletePost(id) {
      axios.delete(`${server.baseURL}/blog/delete?postID=${id}`).then(data => {
        console.log(data);
        window.location.reload();
      });
    }
  }
};
</script>	
	

Within the <script> section of this file, you created a method named fetchPosts() to fetch all posts from the database, and you updated the page with the data returned from the server.

Now, you’ll update the App component of the front-end application in order to create links to the Home and Create components. Open src/App.vue and update it with the following:

~blog-frontend/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>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}
#nav {
  padding: 30px;
  text-align: center;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>	

Apart from including the links to both Home and Create components, you also included the <Style> section, which is the stylesheet for this component and holds the definition of styles for some of the elements on the page. Save and exit the file.

You have created all the required components for your application in this step. Next, you will configure the router file.

Step 9 — Setting Up Routing

After creating all the necessary reusable components, you can now properly configure the router file by updating its content with links to all the components you’ve created. This will ensure that all endpoints within the front-end application are mapped to a particular component for appropriate action. Navigate to ./src/router.js and replace its content with the following:

~blog-frontend/src/router.js

import Vue from 'vue'
import Router from 'vue-router'
import HomeComponent from '@/views/Home';
import EditComponent from '@/components/post/Edit';
import CreateComponent from '@/components/post/Create';
import PostComponent from '@/components/post/Post';

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 },
    { path: '/post/:id', name: 'Post', component: PostComponent }
  ]
});


You imported Router from the vue-router module and instantiated it by passing the mode and routesparameters. The default mode for vue-router is a hash mode, which uses the URL hash to simulate a full URL so that the page won’t be reloaded when the URL changes. In order to make the hash unnecessary, you have used history mode here to achieve URL navigation without a page reload. Finally, within the routes option, you specified the path for the endpoint — a name for the route and the component that should be rendered when the route is called within the application. Save and exit the file.

Now that you have set up routing to the application, you need to include the Bootstrap file to help with pre-built styling for the user interface of the application. To achieve that, open ./public/index.html file in your text editor and include the CDN file for Bootstrap by adding the following content to the file:

~blog-frontend/public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  ...
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
  <title>blog-frontend</title>
</head>
<body>
   ...
</body>
</html>

Save and exit the file, and then restart the application with npm run serve for your blog-frontend, if it is not currently running.

Note: Ensure that both the back-end server and the MongoDB instance are running as well. If otherwise, navigate to the blog-backend from another terminal and run npm run start. Also, start the MongoDB service by running sudo mongod from a new terminal as well.

Navigate to your application at: [http://localhost:8080](http://localhost:8080 "http://localhost:8080"). Now you can test your blog by creating and editing posts.

Click on Create on your application to see the Create Post screen, which relates to and renders the CreateComponent file. Enter values into the input fields and click on the Create Post button to submit a post. Once you are done, the application will redirect you back to the homepage.

The homepage of the application renders the HomeComponent. This component has a method that sends an HTTP call to fetch all posts from the database and displays them to users.

Clicking on the Edit Post button for a particular post will take you to an edit page where you can incorporate any changes and save your post.

In this section, you configured and set up routing for the application. With this in place, your blog application is ready.

Conclusion

In this tutorial, you have explored a new way of structuring a Node.js application by using Nest.js. You created a simple blog application using Nest.js to build the back-end RESTful API and used Vue.js to handle all the front-end logic. Furthermore, you also integrated MongoDB as a database for your Nest.js application.

To learn more about how to add authentication to your application, you can make use of Passport.js, a popular Node.js authentication library. You can learn about Passport.js integration in the Nest.js documentation.

You can find the complete source code for this project here on GitHub. For more information about Nest.js, you can visit the official documentation.

Further Reading

Building a modern app using Nest.js, MongoDB and Vue.js

How to Build a Webstore Using Modern Stack (Nest.js, GraphQL, Apollo) Part 1

Build a web app using Nestjs, Fastify, MongoDB and Angular 8

Deploy a NestJS App with Serverless Framework

Node.js, ExpressJs, MongoDB and Vue.js (MEVN Stack) Application Tutorial

Node.js, ExpressJs, MongoDB and Vue.js (MEVN Stack) Application Tutorial

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.

Dependencies

There 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.

Model

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);

Queries

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);

Controllers

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');
    });

Routes

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 Components

For 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.

MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App

MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App

The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application

The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application

MEVN Tutorial: The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application. Previously we have a tutorial on build chat application using MEAN Stack, now we build this chat application using MEVN (MongoDB, Express.js, Vue.js 2, Node.js) Stack. The different just now we use Vue.js 2 and Axios, we keep using MongoDB, Node.js, Express, and Socket.io.

Table of Contents:
  • Create a New Vue.js 2 Application using Vue-CLI
  • Install Express.js as RESTful API Server
  • Install and Configure Mongoose.js
  • Create REST API for Accessing Chat Data
  • Create Mongoose.js Model for Room and Chat
  • Create Vue.js 2 Component and Routing
  • Add Module for RESTful API Access and Styling UI
  • Modify Vue.js 2 Component for Room List
  • Modify Vue.js 2 Component for Add Room
  • Modify Vue.js 2 Component for Join Room
  • Modify Vue.js 2 Component for Chat Room
  • Integrate Socket.io With Existing Non-Real-time Chat Application
  • Run and Test The Chat Application

The scenario is very simple, just the rooms and the chats for each room. The first page will show the list of the rooms. After the user enters the room and fills the username or nickname then the user enters the chats with other users.

.The following tools, frameworks, and modules are required for this tutorial:

  1. Node.js (use recommended version)
  2. Express.js
  3. MongoDB
  4. Mongoose.js
  5. Vue.js
  6. Vue-CLI
  7. Socket IO
  8. Axios
  9. Terminal (Mac/Linux) or Node.js Command Line (Windows)
  10. IDE or Text Editor (we use Atom)

We assume that you have already installed Node.js and able to run Node.js command line (Windows) or npm on the terminal (MAC/Linux). Open the terminal or Node command line then type this command to install vue-cli.

sudo npm install -g vue-cli

That where we start the tutorial. We will create the MEVN stack Chat application using vue-cli.

1. Create a New Vue.js 2 Application using Vue-CLI

To create a new Vue.js 2 application using vue-cli simply type this command from terminal or Node command line.

vue init webpack mevn-chat

There will be a lot of questions, just leave it as default by always pressing enter key. Next, go to the newly created Vue.js project folder then install all default required modules by type this command.

cd ./mevn-chat

Now, check the Vue.js 2 application by running the application using this command.

npm run dev

Open your browser then go to localhost:8080 and you should see this page when everything still on the track.

2. Install Express.js as RESTful API Server

Close the running Vue.js 2 app first by press ctrl+c then type this command for adding Express.js modules and its dependencies.

npm install --save express body-parser morgan body-parser serve-favicon

Next, create a new folder called bin then add a file called www on the root of the Vue.js project folder.

mkdir bin
touch bin/www

Open and edit www file then add these lines of codes that contains configuration for an HTTP server, PORT, and error handling.

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('mean-app:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

Next, change the default server what run by npm command. Open and edit package.json then replace startvalue inside scripts.

"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run build && node ./bin/www",
  "unit": "jest --config test/unit/jest.conf.js --coverage",
  "e2e": "node test/e2e/runner.js",
  "test": "npm run unit && npm run e2e",
  "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
  "build": "node build/build.js"
},

Next, create app.js in the root of project folder.

touch app.js

Open and edit app.js then add this lines of codes.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var bodyParser = require('body-parser');

var room = require('./routes/room');
var chat = require('./routes/chat');
var app = express();

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({'extended':'false'}));
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/rooms', express.static(path.join(__dirname, 'dist')));
app.use('/api/room', room);
app.use('/api/chat', chat);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

Next, create routes folder then create routes file for the room and chat.

mkdir routes
touch routes/room.js
touch routes/chat.js

Open and edit routes/room.js file then add this lines of codes.

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.send('Express RESTful API');
});

module.exports = router;

Do the same way with routes/chat.js. Now, run the server using this command.

npm start

You will see the previous Vue.js landing page when you point your browser to [http://localhost:3000](http://localhost:3000 "http://localhost:3000"). When you change the address to [http://localhost:3000/api/room](http://localhost:3000/api/room "http://localhost:3000/api/room") or [http://localhost:3000/api/chat](http://localhost:3000/api/chat "http://localhost:3000/api/chat") you will see this page.

3. Install and Configure Mongoose.js

We need to access data from MongoDB. For that, we will install and configure Mongoose.js. On the terminal type this command after stopping the running Express server.

npm install --save mongoose bluebird

Open and edit app.js then add this lines after another variable line.

var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
mongoose.connect('mongodb://localhost/mevn-chat', { promiseLibrary: require('bluebird') })
  .then(() =>  console.log('connection succesful'))
  .catch((err) => console.error(err));

Now, run MongoDB server on different terminal tab or command line or run from the service.

mongod

Next, you can test the connection to MongoDB run again the Node application and you will see this message on the terminal.

connection succesful

If you are still using built-in Mongoose Promise library, you will get this deprecated warning on the terminal.

(node:42758) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

That’s the reason why we added bluebird modules and register it as Mongoose Promise library.

4. Create Mongoose.js Model for Room and Chat

Add a models folder on the root of project folder for hold Mongoose.js model files then add Javascript file for Room and Chat.

mkdir models
touch models/Room.js
touch models/Chat.js

Next, open and edit models/Room.js then add this lines of codes.

var mongoose = require('mongoose'), Schema = mongoose.Schema;

var RoomSchema = new mongoose.Schema({
  room_name: String,
  created_date: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Room', RoomSchema);

Open and edit models/Chat.js then add this lines of codes.

var mongoose = require('mongoose'), Schema = mongoose.Schema;

var ChatSchema = new mongoose.Schema({
  room : { type: Schema.Types.ObjectId, ref: 'Room' },
  nickname: String,
  message: String,
  created_date: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Chat', ChatSchema);
5. Create Vue.js Component and Routing

Open and edit again routes/room.js then replace all codes with this.

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Room = require('../models/Room.js');

/* GET ALL ROOMS */
router.get('/', function(req, res, next) {
  Room.find(function (err, products) {
    if (err) return next(err);
    res.json(products);
  });
});

/* GET SINGLE ROOM BY ID */
router.get('/:id', function(req, res, next) {
  Room.findById(req.params.id, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* SAVE ROOM */
router.post('/', function(req, res, next) {
  Room.create(req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* UPDATE ROOM */
router.put('/:id', function(req, res, next) {
  Room.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* DELETE ROOM */
router.delete('/:id', function(req, res, next) {
  Room.findByIdAndRemove(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

module.exports = router;

Open and edit again routes/chat.js then replace all codes with this.

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Chat = require('../models/Chat.js');

/* GET ALL CHATS */
router.get('/', function(req, res, next) {
  Chat.find(function (err, products) {
    if (err) return next(err);
    res.json(products);
  });
});

/* GET SINGLE CHAT BY ID */
router.get('/:id', function(req, res, next) {
  Chat.findById(req.params.id, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* SAVE CHAT */
router.post('/', function(req, res, next) {
  Chat.create(req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* UPDATE CHAT */
router.put('/:id', function(req, res, next) {
  Chat.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* DELETE CHAT */
router.delete('/:id', function(req, res, next) {
  Chat.findByIdAndRemove(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

module.exports = router;

Run again the Express server then open the other terminal or command line to test the Restful API by type this command.

curl -i -H "Accept: application/json" localhost:3000/api/room

If that command return response like below then REST API is ready to go.

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w"
Date: Sun, 05 Aug 2018 13:11:30 GMT
Connection: keep-alive

[]

Now, let’s populate Room collection with initial data that sent from RESTful API. Run this command to populate it.

curl -i -X POST -H "Content-Type: application/json" -d '{ "room_name":"Javascript" }' localhost:3000/api/room

You will see this response to the terminal if success.

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 109
ETag: W/"6d-OGpcih/JWvJGrYAhMP+KBYQOvNQ"
Date: Sun, 05 Aug 2018 13:35:50 GMT
Connection: keep-alive

{"_id":"5b66fd3581b9291558dc90b7","room_name":"Javascript","created_date":"2018-08-05T13:35:49.803Z","__v":0}

6. Create Vue.js 2 Component and Routing

Now, it’s time for Vue.js 2 or front end part. First, create or add the component of the room list, add a room, join a room, chat room. Create all of those files into the components folder.

touch src/components/RoomList.vue
touch src/components/AddRoom.vue
touch src/components/JoinRoom.vue
touch src/components/ChatRoom.vue

Now, open and edit src/router/index.js then add the import for all above new components.

import Vue from 'vue'
import Router from 'vue-router'
import RoomList from '@/components/RoomList'
import AddRoom from '@/components/AddRoom'
import JoinRoom from '@/components/JoinRoom'
import ChatRoom from '@/components/ChatRoom'

Add the router to each component or page.

export default new Router({
  routes: [
    {
      path: '/',
      name: 'RoomList',
      component: RoomList
    },
    {
      path: '/add-room',
      name: 'AddRoom',
      component: AddRoom
    },
    {
      path: '/join-room/:id',
      name: 'JoinRoom',
      component: JoinRoom
    },
    {
      path: '/chat-room/:id/:nickname',
      name: 'ChatRoom',
      component: ChatRoom
    }
  ]
})

7. Add Axios and Bootstrap-Vue

For UI or styling, we are using Bootstrap Vue. BootstrapVue use to build responsive, mobile-first projects on the web using Vue.js and the world's most popular front-end CSS library Bootstrap v4. To install Bootstrap-Vue type this command on the terminal.

npm i bootstrap-vue [email protected]

Open and edit src/main.js then add the imports for Bootstrap-Vue.

import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import App from './App'
import router from './router'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Add this line after Vue.config.

Vue.use(BootstrapVue)

Next, we are using Axio for accessing REST API provided by Express.js. Axios is a promise-based HTTP client for the browser and node.js. To install it, in the terminal type this command.

npm install axios --save

8. Modify Vue.js 2 Component for Room List

Now, open and edit src/components/RoomList.vue then add this lines of codes.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Room List
        <b-link href="#/add-room">(Add Room)</b-link>
      </h2>
      <b-table striped hover :items="rooms" :fields="fields">
        <template slot="actions" scope="row">
          <b-btn size="sm" @click.stop="join(row._id)">Join</b-btn>
        </template>
      </b-table>
      <ul v-if="errors && errors.length">
        <li v-for="error of errors">
          {{error.message}}
        </li>
      </ul>
    </b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'BookList',
  data () {
    return {
      fields: {
        room_name: { label: 'Room Name', sortable: true, 'class': 'text-center' },
        created_date: { label: 'Created Date', sortable: true },
        actions: { label: 'Action', 'class': 'text-center' }
      },
      rooms: [],
      errors: []
    }
  },
  created () {
    axios.get(`http://localhost:3000/api/room`)
    .then(response => {
      this.rooms = response.data
    })
    .catch(e => {
      this.errors.push(e)
    })
  },
  methods: {
    join (id) {
      this.$router.push({
        name: 'JoinRoom',
        params: { id: id }
      })
    }
  }
}
</script>

There are template and script in one file. The template block contains HTML tags. Script block contains variables, page lifecycle and methods or functions.

9. Modify Vue.js 2 Component for Add Room

Now, open and edit src/components/AddRoom.vue then add this lines of codes.

<template>
  <b-row>
    <b-col align-self="start">&nbsp;</b-col>
    <b-col cols="6" align-self="center">
      <h2>
        Add Room
        <b-link href="#/">(Room List)</b-link>
      </h2>
      <b-form @submit="onSubmit">
        <b-form-group id="fieldsetHorizontal"
                  horizontal
                  :label-cols="4"
                  breakpoint="md"
                  label="Enter Room Name">
          <b-form-input id="room_name" :state="state" v-model.trim="room.room_name"></b-form-input>
        </b-form-group>
        <b-button type="submit" variant="primary">Save</b-button>
      </b-form>
    </b-col>
    <b-col align-self="end">&nbsp;</b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'AddRoom',
  data () {
    return {
      room: {}
    }
  },
  methods: {
    onSubmit (evt) {
      evt.preventDefault()
      axios.post(`http://localhost:3000/api/room`, this.room)
      .then(response => {
        this.$router.push({
          name: 'RoomList'
        })
      })
      .catch(e => {
        this.errors.push(e)
      })
    }
  }
}
</script>

That code contains the template for room form, the script that contains Vue.js 2 codes for hold room model and methods for saving room to RESTful API.

10. Modify Vue.js 2 Component for Join Room

Now, open and edit src/components/JoinRoom.vue then add this lines of codes.

<template>
  <b-row>
    <b-col cols="6">
      <h2>
        Join Room
        <b-link href="#/">(Room List)</b-link>
      </h2>
      <b-form @submit="onSubmit">
        <b-form-group id="fieldsetHorizontal"
                  horizontal
                  :label-cols="4"
                  breakpoint="md"
                  label="Enter Nickname">
          <b-form-input id="nickname" :state="state" v-model.trim="chat.nickname"></b-form-input>
        </b-form-group>
        <b-button type="submit" variant="primary">Join</b-button>
      </b-form>
    </b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'JoinRoom',
  data () {
    return {
      chat: {}
    }
  },
  methods: {
    onSubmit (evt) {
      evt.preventDefault()
      this.chat.room = this.$route.params.id
      this.chat.message = this.chat.nickname + ' join the room'
      axios.post(`http://localhost:3000/api/chat`, this.chat)
      .then(response => {
        this.$router.push({
          name: 'ChatRoom',
          params: { id: this.$route.params.id, nickname: response.data.nickname }
        })
      })
      .catch(e => {
        this.errors.push(e)
      })
    }
  }
}
</script>

That code contains the template for join room form, the script that contains Vue.js 2 codes for hold chat model and methods for saving room to RESTful API.

11. Modify Vue.js 2 Component for Chat Room

Now, open and edit src/components/JoinRoom.vue then add this lines of codes.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Chat Room
      </h2>
      <b-list-group class="panel-body">
        <b-list-group-item v-for="(item, index) in chats" class="chat">
          <div class="left clearfix" v-if="item.nickname === nickname">
            <b-img left src="http://placehold.it/50/55C1E7/fff&text=ME" rounded="circle" width="75" height="75" alt="img" class="m-1" />
            <div class="chat-body clearfix">
              <div class="header">
                <strong class="primary-font">{{ item.nickname }}</strong> <small class="pull-right text-muted">
                <span class="glyphicon glyphicon-time"></span>{{ item.created_date }}</small>
              </div>
              <p>{{ item.message }}</p>
            </div>
          </div>
          <div class="right clearfix" v-else>
            <b-img right src="http://placehold.it/50/55C1E7/fff&text=U" rounded="circle" width="75" height="75" alt="img" class="m-1" />
            <div class="chat-body clearfix">
              <div class="header">
                <strong class="primary-font">{{ item.nickname }}</strong> <small class="pull-right text-muted">
                <span class="glyphicon glyphicon-time"></span>{{ item.created_date }}</small>
              </div>
              <p>{{ item.message }}</p>
            </div>
          </div>
        </b-list-group-item>
      </b-list-group>
      <ul v-if="errors && errors.length">
        <li v-for="error of errors">
          {{error.message}}
        </li>
      </ul>
      <b-form @submit="onSubmit" class="chat-form">
        <b-input-group prepend="Message">
          <b-form-input id="message" :state="state" v-model.trim="chat.message"></b-form-input>
          <b-input-group-append>
            <b-btn type="submit" variant="info">Send</b-btn>
          </b-input-group-append>
        </b-input-group>
      </b-form>
    </b-col>
  </b-row>
</template>

<script>

import axios from 'axios'

export default {
  name: 'ChatRoom',
  data () {
    return {
      chats: [],
      errors: [],
      nickname: this.$route.params.nickname,
      chat: {}
    }
  },
  created () {
    axios.get(`http://localhost:3000/api/chat/` + this.$route.params.id)
    .then(response => {
      this.chats = response.data
    })
    .catch(e => {
      this.errors.push(e)
    })
  },
  methods: {
    logout (id) {
      this.$router.push({
        name: 'JoinRoom',
        params: { id: id }
      })
    },
    onSubmit (evt) {
      evt.preventDefault()
      this.chat.room = this.$route.params.id
      this.chat.nickname = this.$route.params.nickname
      axios.post(`http://localhost:3000/api/chat`, this.chat)
      .then(response => {
        // this.$router.push({
        //   name: 'ChatRoom',
        //   params: { id: this.$route.params.id, nickname: response.data.nickname }
        // })
      })
      .catch(e => {
        this.errors.push(e)
      })
    }
  }
}
</script>

<style>
  .chat .left .chat-body {
    text-align: left;
    margin-left: 100px;
  }

  .chat .right .chat-body {
    text-align: right;
    margin-right: 100px;
  }

  .chat .chat-body p {
    margin: 0;
    color: #777777;
  }

  .panel-body {
    overflow-y: scroll;
    height: 350px;
  }

  .chat-form {
    margin: 20px auto;
    width: 80%;
  }
</style>

That code contains the template of the main chat application consist of chat list and sends message form.

12. Integrate Socket.io With Existing Non-Real-time Chat Application

Previous steps show you a regular and non-realtime transaction chat application. Now, we will make it real-time by using Socket.io. First, install socket.io module by type this command.

npm install --save socketio socket.io-client

Next, open and edit routes/chat.js then declare the Socket IO and http module.

var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);

Add this lines of codes for Socket IO functions.

server.listen(4000);

// socket io
io.on('connection', function (socket) {
  console.log('User connected');
  socket.on('disconnect', function() {
    console.log('User disconnected');
  });
  socket.on('save-message', function (data) {
    console.log(data);
    io.emit('new-message', { message: data });
  });
});

In that code, we are running Socket.io to listen for ‘save-message’ that emitted from the client and emit ‘new-message’ to the clients. Next, open and edit src/components/JoinRoom.vue then add this import.

import * as io from 'socket.io-client'

Declare Socket IO variable.

data () {
  return {
    chat: {},
    socket: io('http://localhost:4000')
  }
},

Add Socket IO emit function after successful join room.

axios.post(`http://localhost:3000/api/chat`, this.chat)
.then(response => {
  this.socket.emit('save-message', { room: this.chat.room, nickname: this.chat.nickname, message: 'Join this room', created_date: new Date() });
  this.$router.push({
    name: 'ChatRoom',
    params: { id: this.$route.params.id, nickname: response.data.nickname }
  })
})
.catch(e => {
  this.errors.push(e)
})

Next, open and edit src/components/ChatRoom.vue then add this imports and use as Vue module.

import Vue from 'vue'
import * as io from 'socket.io-client'
import VueChatScroll from 'vue-chat-scroll'
Vue.use(VueChatScroll)

Declare Socket IO variable.

data () {
  return {
    chats: [],
    errors: [],
    nickname: this.$route.params.nickname,
    chat: {},
    socket: io('http://localhost:4000')
  }
},

Add this Socket IO on function to created method.

created () {
  axios.get(`http://localhost:3000/api/chat/` + this.$route.params.id)
  .then(response => {
    this.chats = response.data
  })
  .catch(e => {
    this.errors.push(e)
  })

  this.socket.on('new-message', function (data) {
    if(data.message.room === this.$route.params.id) {
      this.chats.push(data.message)
    }
  }.bind(this))
},

Add Logout function inside methods and add Socket IO emit method in the POST response.

methods: {
  logout () {
    this.socket.emit('save-message', { room: this.chat.room, nickname: this.chat.nickname, message: this.chat.nickname + ' left this room', created_date: new Date() });
    this.$router.push({
      name: 'RoomList'
    })
  },
  onSubmit (evt) {
    evt.preventDefault()
    this.chat.room = this.$route.params.id
    this.chat.nickname = this.$route.params.nickname
    axios.post(`http://localhost:3000/api/chat`, this.chat)
    .then(response => {
      this.socket.emit('save-message', response.data)
      this.chat.message = ''
    })
    .catch(e => {
      this.errors.push(e)
    })
  }
}

Finally, to make Chat list always scroll to the bottom of Chat element add install this module.

npm install --save vue-chat-scroll

That module already imported and declared above. Next, add to the Chat element.

<b-list-group class="panel-body" v-chat-scroll>
  ...
</b-list-group>

13. Run and Test The MEVN (Vue.js 2) Chat Application**

To run this MEVN (Vue.js 2) Chat Application locally, make sure MongoDB service is running. Type this command to build the Vue.js 2 application then run the Express.js application.

npm start

Next, open the different browser (ex: Chrome and Firefox) then go to the localhost:3000 on both of the browsers. You will see this page and you can start Chat.

That it’s, the MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App. You can find the full working source code on our GitHub.

Thanks!

=============================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ MERN Stack Front To Back: Full Stack React, Redux & Node.js

☞ The Complete Node.js Developer Course (3rd Edition)

☞ Angular & NodeJS - The MEAN Stack Guide

☞ NodeJS - The Complete Guide (incl. MVC, REST APIs, GraphQL)

☞ Docker for Node.js Projects From a Docker Captain

☞ Intro To MySQL With Node.js - Learn To Use MySQL with Node!

☞ Node.js Absolute Beginners Guide - Learn Node From Scratch

☞ React Node FullStack - Social Network from Scratch to Deploy

☞ Selenium WebDriver - JavaScript nodeJS webdriver IO & more!

☞ Complete Next.js with React & Node - Beautiful Portfolio App

☞ Build a Blockchain & Cryptocurrency | Full-Stack Edition

How to Use Express.js, Node.js and MongoDB.js

How to Use Express.js, Node.js and MongoDB.js

In this post, I will show you how to use Express.js, Node.js and MongoDB.js. We will be creating a very simple Node application, that will allow users to input data that they want to store in a MongoDB database. It will also show all items that have been entered into the database.

In this post, I will show you how to use Express.js, Node.js and MongoDB.js. We will be creating a very simple Node application, that will allow users to input data that they want to store in a MongoDB database. It will also show all items that have been entered into the database.

Creating a Node Application

To get started I would recommend creating a new database that will contain our application. For this demo I am creating a directory called node-demo. After creating the directory you will need to change into that directory.

mkdir node-demo
cd node-demo

Once we are in the directory we will need to create an application and we can do this by running the command
npm init

This will ask you a series of questions. Here are the answers I gave to the prompts.

The first step is to create a file that will contain our code for our Node.js server.

touch app.js

In our app.js we are going to add the following code to build a very simple Node.js Application.

var express = require("express");
var app = express();
var port = 3000;
 
app.get("/", (req, res) => {
&nbsp;&nbsp;res.send("Hello World");
});
 
app.listen(port, () => {
  console.log("Server listening on port " + port);
});

What the code does is require the express.js application. It then creates app by calling express. We define our port to be 3000.

The app.use line will listen to requests from the browser and will return the text “Hello World” back to the browser.

The last line actually starts the server and tells it to listen on port 3000.

Installing Express

Our app.js required the Express.js module. We need to install express in order for this to work properly. Go to your terminal and enter this command.

npm install express --save

This command will install the express module into our package.json. The module is installed as a dependency in our package.json as shown below.

To test our application you can go to the terminal and enter the command

node app.js

Open up a browser and navigate to the url http://localhost:3000

You will see the following in your browser

Creating Website to Save Data to MongoDB Database

Instead of showing the text “Hello World” when people view your application, what we want to do is to show a place for user to save data to the database.

We are going to allow users to enter a first name and a last name that we will be saving in the database.

To do this we will need to create a basic HTML file. In your terminal enter the following command to create an index.html file.

touch index.html

In our index.html file we will be creating an input filed where users can input data that they want to have stored in the database. We will also need a button for users to click on that will add the data to the database.

Here is what our index.html file looks like.

<!DOCTYPE html>
<html>
  <head>
    <title>Intro to Node and MongoDB<title>
  <head>

  <body>
    <h1>Into to Node and MongoDB<&#47;h1>
    <form method="post" action="/addname">
      <label>Enter Your Name<&#47;label><br>
      <input type="text" name="firstName" placeholder="Enter first name..." required>
      <input type="text" name="lastName" placeholder="Enter last name..." required>
      <input type="submit" value="Add Name">
    </form>
  <body>
<html>

If you are familiar with HTML, you will not find anything unusual in our code for our index.html file. We are creating a form where users can input their first name and last name and then click an “Add Name” button.

The form will do a post call to the /addname endpoint. We will be talking about endpoints and post later in this tutorial.

Displaying our Website to Users

We were previously displaying the text “Hello World” to users when they visited our website. Now we want to display our html file that we created. To do this we will need to change the app.use line our our app.js file.

We will be using the sendFile command to show the index.html file. We will need to tell the server exactly where to find the index.html file. We can do that by using a node global call __dirname. The __dirname will provide the current directly where the command was run. We will then append the path to our index.html file.

The app.use lines will need to be changed to
app.use("/", (req, res) => {   res.sendFile(__dirname + "/index.html"); });

Once you have saved your app.js file, we can test it by going to terminal and running node app.js

Open your browser and navigate to “http://localhost:3000”. You will see the following

Connecting to the Database

Now we need to add our database to the application. We will be connecting to a MongoDB database. I am assuming that you already have MongoDB installed and running on your computer.

To connect to the MongoDB database we are going to use a module called Mongoose. We will need to install mongoose module just like we did with express. Go to your terminal and enter the following command.
npm install mongoose --save

This will install the mongoose model and add it as a dependency in our package.json.

Connecting to the Database

Now that we have the mongoose module installed, we need to connect to the database in our app.js file. MongoDB, by default, runs on port 27017. You connect to the database by telling it the location of the database and the name of the database.

In our app.js file after the line for the port and before the app.use line, enter the following two lines to get access to mongoose and to connect to the database. For the database, I am going to use “node-demo”.

var mongoose = require("mongoose"); mongoose.Promise = global.Promise; mongoose.connect("mongodb://localhost:27017/node-demo");

Creating a Database Schema

Once the user enters data in the input field and clicks the add button, we want the contents of the input field to be stored in the database. In order to know the format of the data in the database, we need to have a Schema.

For this tutorial, we will need a very simple Schema that has only two fields. I am going to call the field firstName and lastName. The data stored in both fields will be a String.

After connecting to the database in our app.js we need to define our Schema. Here are the lines you need to add to the app.js.
var nameSchema = new mongoose.Schema({   firstName: String,   lastNameName: String });

Once we have built our Schema, we need to create a model from it. I am going to call my model “DataInput”. Here is the line you will add next to create our mode.
var User = mongoose.model("User", nameSchema);

Creating RESTful API

Now that we have a connection to our database, we need to create the mechanism by which data will be added to the database. This is done through our REST API. We will need to create an endpoint that will be used to send data to our server. Once the server receives this data then it will store the data in the database.

An endpoint is a route that our server will be listening to to get data from the browser. We already have one route that we have created already in the application and that is the route that is listening at the endpoint “/” which is the homepage of our application.

HTTP Verbs in a REST API

The communication between the client(the browser) and the server is done through an HTTP verb. The most common HTTP verbs are
GET, PUT, POST, and DELETE.

The following table explains what each HTTP verb does.

HTTP Verb Operation
GET Read
POST Create
PUT Update
DELETE Delete

As you can see from these verbs, they form the basis of CRUD operations that I talked about previously.

Building a CRUD endpoint

If you remember, the form in our index.html file used a post method to call this endpoint. We will now create this endpoint.

In our previous endpoint we used a “GET” http verb to display the index.html file. We are going to do something very similar but instead of using “GET”, we are going to use “POST”. To get started this is what the framework of our endpoint will look like.

app.post("/addname", (req, res) => {
 
});
Express Middleware

To fill out the contents of our endpoint, we want to store the firstName and lastName entered by the user into the database. The values for firstName and lastName are in the body of the request that we send to the server. We want to capture that data, convert it to JSON and store it into the database.

Express.js version 4 removed all middleware. To parse the data in the body we will need to add middleware into our application to provide this functionality. We will be using the body-parser module. We need to install it, so in your terminal window enter the following command.

npm install body-parser --save

Once it is installed, we will need to require this module and configure it. The configuration will allow us to pass the data for firstName and lastName in the body to the server. It can also convert that data into JSON format. This will be handy because we can take this formatted data and save it directly into our database.

To add the body-parser middleware to our application and configure it, we can add the following lines directly after the line that sets our port.

var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
Saving data to database

Mongoose provides a save function that will take a JSON object and store it in the database. Our body-parser middleware, will convert the user’s input into the JSON format for us.

To save the data into the database, we need to create a new instance of our model that we created early. We will pass into this instance the user’s input. Once we have it then we just need to enter the command “save”.

Mongoose will return a promise on a save to the database. A promise is what is returned when the save to the database completes. This save will either finish successfully or it will fail. A promise provides two methods that will handle both of these scenarios.

If this save to the database was successful it will return to the .then segment of the promise. In this case we want to send text back the user to let them know the data was saved to the database.

If it fails it will return to the .catch segment of the promise. In this case, we want to send text back to the user telling them the data was not saved to the database. It is best practice to also change the statusCode that is returned from the default 200 to a 400. A 400 statusCode signifies that the operation failed.

Now putting all of this together here is what our final endpoint will look like.

app.post("/addname", (req, res) => {
  var myData = new User(req.body);
  myData.save()
    .then(item => {
      res.send("item saved to database");
    })
    .catch(err => {
      res.status(400).send("unable to save to database");
    });
});
Testing our code

Save your code. Go to your terminal and enter the command node app.js to start our server. Open up your browser and navigate to the URL “http://localhost:3000”. You will see our index.html file displayed to you.

Make sure you have mongo running.

Enter your first name and last name in the input fields and then click the “Add Name” button. You should get back text that says the name has been saved to the database like below.

Access to Code

The final version of the code is available in my Github repo. To access the code click here. Thank you for reading !