AngularJS tutorial for beginners with NodeJS, ExpressJS and MongoDB

AngularJS tutorial for beginners with NodeJS, ExpressJS and MongoDB

This tutorial is meant to be as clear as possible. At the same time, we are going to cover the concepts that you will need most of the time. All the good stuff without the fat :)

This tutorial is meant to be as clear as possible. At the same time, we are going to cover the concepts that you will need most of the time. All the good stuff without the fat :)

MEAN Stack tutorial series:

  1. AngularJS tutorial for beginners (Part I) 👈 you are here
  2. Creating RESTful APIs with NodeJS and MongoDB Tutorial (Part II)
  3. MEAN Stack Tutorial: MongoDB, ExpressJS, AngularJS and NodeJS (Part III)

We are going to start building all the examples in a single HTML file! It embedded JavaScript and NO styles/CSS for simplicity. Don’t worry, in the next tutorials, we will learn how to split use Angular modules. We are going to break down the code, add testing to it and styles.

What is Angular.js?

Angular.js is a MVW (Model-View-Whatever) open-source JavaScript web framework that facilitates the creation of single-page applications (SPA) and data-driven apps.

AngularJS vs jQuery vs BackboneJS vs EmberJS

TL; DR: AngularJS is awesome for building testable single page applications (SPA). Also, excel with data-driven and CRUD apps. Show me the code!.

AngularJS motto is

HTML enhanced for web apps!
It extends standard HTML tags and properties to bind events and data into it using JavaScript. It has a different approach to other libraries. jQuery, Backbone.Js, Ember.js and similar… they are more leaned towards “Unobtrusive JavaScript”.

Traditional JavaScript frameworks, use IDs and classes in the elements. That gives the advantage of separating structure (HTML) from behavior (Javascript). Yet, it does not do any better on code complexity and readability. Angular instead declares the event handlers right in the element that they act upon.

Times have changed since then. Let’s examine how AngularJS tries to ease code complexity and readability:

  • Unit testing ready: JavaScript is, usually, hard to unit test. When you have DOM manipulations and business logic together (e.g. jQuery based code). AngularJS keeps DOM manipulation in the HTML and business logic separated. Data and dependencies are $injected as needed.
  • DOM manipulation where they are used. It decouples DOM manipulation from application logic.
  • AngularJS is also excellent for single-page applications (SPA).
  • Different browsers implement features differently, but fret not. Angular’s directive (or HTML extensions) take care of the differences for you.
  • Global namespace expressions and methods definitions are scoped within controllers. So, they do not pollute the global namespace.
  • Data models are plain old JavaScript objects (POJO).
  • Write less code: AngualarJS features save you from much boilerplate code.
  • AngularJS provides solutions for writing modular code and dependencies management.

Without further ado, let’s dive in!

2. AngularJS Main Components

AngularJS has an extensive API and components. In this tutorial we are going to focus on the most important ones, such as directives, modules, services, controllers and related concepts.

2.1 AngularJS Directives

The first concept you need to know about AngularJS is what are directives.

Directives are extensions of HTML markups. They could take the form of attributes, element names, CSS class and or even HTML comments. When the AngularJS framework is loaded, everything inside ng-app it’s compiled. The directives are bound to data, events, and DOM transformations.

Notice in the following example that there are two directives: ng-app and ng-model.

Notice in the following example that there are two directives: ng-app and ng-model.

<html ng-app>
<head>
  <title>Hello World in AngularJS</title>
</head>
<body>

<input ng-model="name"> Hello {{ name }}

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
</body>
</html>

We going to learn about some of the main built-in directives as we go:

  • Unit testing ready: JavaScript is, usually, hard to unit test. When you have DOM manipulations and business logic together (e.g. jQuery based code). AngularJS keeps DOM manipulation in the HTML and business logic separated. Data and dependencies are $injected as needed.
  • DOM manipulation where they are used. It decouples DOM manipulation from application logic.
  • AngularJS is also excellent for single-page applications (SPA).
  • Different browsers implement features differently, but fret not. Angular’s directive (or HTML extensions) take care of the differences for you.
  • Global namespace expressions and methods definitions are scoped within controllers. So, they do not pollute the global namespace.
  • Data models are plain old JavaScript objects (POJO).
  • Write less code: AngualarJS features save you from much boilerplate code.
  • AngularJS provides solutions for writing modular code and dependencies management.
2.2 AngularJS Data Binding

Data binding is an AngularJS feature that synchronizes your model data with your HTML. That’s great because models are the “single source of truth”. You do not have to worry about updating them. Here’s a graph from docs.angularjs.org.

Whenever the HTML is changed, the model gets updated. Wherever the model gets updated it is reflected in HTML.

2.3 AngularJS Scope

$scope it is an object that contains all the data to which HTML is bound. They are the glue your javascript code (controllers) and the view (HTML). Everything that is attached to the $scope, it is $watched by AngularJS and updated.

Scopes can be bound to javascript functions. Also, you could have more than one $scope and inherit from outer ones. More on this, in the controller’s section.

2.4 AngularJS Controllers

Angular.js controllers are code that “controls” certain sections containing DOM elements. They encapsulate the behavior, callbacks and glue $scope models with views. Let’s see an example to drive the concept home:

<body ng-controller="TodoController">
  <ul>
    <li ng-repeat="todo in todos">
      <input type="checkbox" ng-model="todo.completed">
      {% raw  %}{{ todo.name }}{% endraw %}
    </li>
  </ul>

  <script>
    function TodoController($scope){
      $scope.todos = [
        { name: 'Master HTML/CSS/Javascript', completed: true },
        { name: 'Learn AngularJS', completed: false },
        { name: 'Build NodeJS backend', completed: false },
        { name: 'Get started with ExpressJS', completed: false },
        { name: 'Setup MongoDB database', completed: false },
        { name: 'Be awesome!', completed: false },
      ]
    }
  </script>
</body>

As you might notice we have new friends: ng-controller, ng-repeat and $scope.

  • Unit testing ready: JavaScript is, usually, hard to unit test. When you have DOM manipulations and business logic together (e.g. jQuery based code). AngularJS keeps DOM manipulation in the HTML and business logic separated. Data and dependencies are $injected as needed.
  • DOM manipulation where they are used. It decouples DOM manipulation from application logic.
  • AngularJS is also excellent for single-page applications (SPA).
  • Different browsers implement features differently, but fret not. Angular’s directive (or HTML extensions) take care of the differences for you.
  • Global namespace expressions and methods definitions are scoped within controllers. So, they do not pollute the global namespace.
  • Data models are plain old JavaScript objects (POJO).
  • Write less code: AngualarJS features save you from much boilerplate code.
  • AngularJS provides solutions for writing modular code and dependencies management.
2.5 AngularJS Modules

Modules are a way to encapsulate different parts of your application. They allow reusing code in other places. Here’s an example of how to rewrite our controller using modules.

angular.module('app', [])
  .controller('TodoController', ['$scope', function ($scope) {
    $scope.todos = [
      { title: 'Learn Javascript', completed: true },
      { title: 'Learn Angular.js', completed: false },
      { title: 'Love this tutorial', completed: true },
      { title: 'Learn Javascript design patterns', completed: false },
      { title: 'Build Node.js backend', completed: false },
    ];
  }]);

Using modules brings many advantages. They can be loaded in any order, and parallel dependency loading. Also, tests can only load the required modules and keep it fast, clear view of the dependencies.

2.6 AngularJS Templates

Templates contain HTML and Angular elements (directives, markup, filters or form controls). They can be cached and referenced by an id.

Here’s an example:

<script type="text/ng-template" id="/todos.html">
  <ul>
    <li ng-repeat="todo in todos">
      <input type="checkbox" ng-model="todo.completed">
      {{ todo.name }}
    </li>
  </ul>
</script>

Does the code inside looks familiar? ;)

Notice they are inside the script and has a type of text/ng-template.

2.7 AngularJS Routes (ngRoutes)

ngRoutes module allows changing what we see in the app depending on the URL (route). It, usually, uses templates to inject the HTML into the app.

It does not come with AngularJS core module, so we have to list it as a dependency. We are going to get it from Google CDN:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular-route.min.js"></script>

NEW FEATURE: add notes to the todo tasks. Let’s start with the routes!

angular.module('app', ['ngRoute'])
  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: '/todos.html',
        controller: 'TodoController'
      });
  }]);

  • Unit testing ready: JavaScript is, usually, hard to unit test. When you have DOM manipulations and business logic together (e.g. jQuery based code). AngularJS keeps DOM manipulation in the HTML and business logic separated. Data and dependencies are $injected as needed.
  • DOM manipulation where they are used. It decouples DOM manipulation from application logic.
  • AngularJS is also excellent for single-page applications (SPA).
  • Different browsers implement features differently, but fret not. Angular’s directive (or HTML extensions) take care of the differences for you.
  • Global namespace expressions and methods definitions are scoped within controllers. So, they do not pollute the global namespace.
  • Data models are plain old JavaScript objects (POJO).
  • Write less code: AngualarJS features save you from much boilerplate code.
  • AngularJS provides solutions for writing modular code and dependencies management.
2.8 AngularJS Services (Factories)

Notice that if you want to create a 2nd controller and share $scope.todos it is not possible right now. That is when services become handy. Services are a way to inject data dependencies into controllers. They are created through factories. Let’s see it in action:

angular.module('app', ['ngRoute'])

  .factory('Todos', function(){
    return [
      { name: 'AngularJS Directives', completed: true },
      { name: 'Data binding', completed: true },
      { name: '$scope', completed: true },
      { name: 'Controllers and Modules', completed: true },
      { name: 'Templates and routes', completed: true },
      { name: 'Filters and Services', completed: false },
      { name: 'Get started with Node/ExpressJS', completed: false },
      { name: 'Setup MongoDB database', completed: false },
      { name: 'Be awesome!', completed: false },
    ];
  })

  .controller('TodoController', ['$scope', 'Todos', function ($scope, Todos) {
    $scope.todos = Todos;
  }])

We are now injecting the data dependency Todo into the controllers. This way we could reuse the data to any controller or module that we need to. This is not only used for static data like the array. But we could also do server calls using $http or even RESTful $resource.

This is what is happening:

  1. AngularJS tutorial for beginners (Part I) 👈 you are here
  2. Creating RESTful APIs with NodeJS and MongoDB Tutorial (Part II)
  3. MEAN Stack Tutorial: MongoDB, ExpressJS, AngularJS and NodeJS (Part III)

NOTE: in codepen, you will not see the URL. If you want to see it changing, you can download the whole example an open it from here.

2.9 AngularJS Filters

Filters allow you to format and transform data. They change the output of expressions inside the curly braces. AngularJS comes with a bunch of useful filters.

Built-in Filters:

  • Unit testing ready: JavaScript is, usually, hard to unit test. When you have DOM manipulations and business logic together (e.g. jQuery based code). AngularJS keeps DOM manipulation in the HTML and business logic separated. Data and dependencies are $injected as needed.
  • DOM manipulation where they are used. It decouples DOM manipulation from application logic.
  • AngularJS is also excellent for single-page applications (SPA).
  • Different browsers implement features differently, but fret not. Angular’s directive (or HTML extensions) take care of the differences for you.
  • Global namespace expressions and methods definitions are scoped within controllers. So, they do not pollute the global namespace.
  • Data models are plain old JavaScript objects (POJO).
  • Write less code: AngualarJS features save you from much boilerplate code.
  • AngularJS provides solutions for writing modular code and dependencies management.

Note you can also chain many filters and also define your own filters.

HTML enhanced for web apps!

<script type="text/ng-template" id="/todos.html">
  Search: <input type="text" ng-model="search.name">
  <ul>
    <li ng-repeat="todo in todos | filter: search">
      <input type="checkbox" ng-model="todo.completed">
      <a href="#/{{$index}}">{{todo.name}}</a>
    </li>
  </ul>
</script>

Notice that we are using search.name in the ng-model for search. That will limit the search to the name attribute and search.notes will look inside the notes only. Guest what search would do then? Precisely! It searches in all the attributes. Fork the following example and try it out:

ng-test

Congrats, you have reached this far! It is time to test what you have learned. Test-Driven Learning (TDL) ;). Here’s the challenge: open this file on your favorite code editor. Copy the boilerplate code and built the full app that we just build in the previous examples. Of course, you can take a peek from time to time if you get stuck ;)

Download this file as…:

index.html

ng-solution

This is the full solution and you can see it live in here.

<html ng-app="app">
<head>
  <title>ngTodo</title>
</head>
<body>

<ng-view></ng-view>

<!-- Libraries -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular-route.min.js"></script>

<!-- Template -->
<script type="text/ng-template" id="/todos.html">
  Search: <input type="text" ng-model="search.name">
  <ul>
    <li ng-repeat="todo in todos | filter: search">
      <input type="checkbox" ng-model="todo.completed">
      <a href="#/{{$index}}">{{todo.name}}</a>
    </li>
  </ul>
</script>

<script type="text/ng-template" id="/todoDetails.html">
  <h1>{{ todo.name }}</h1>
  completed: <input type="checkbox" ng-model="todo.completed">
  note: <textarea>{{ todo.note }}</textarea>
</script>

<script>
  angular.module('app', ['ngRoute'])
    //---------------
    // Services
    //---------------
    .factory('Todos', function(){
      return [
        { name: 'AngularJS Directives', completed: true, note: 'add notes...' },
        { name: 'Data binding', completed: true, note: 'add notes...' },
        { name: '$scope', completed: true, note: 'add notes...' },
        { name: 'Controllers and Modules', completed: true, note: 'add notes...' },
        { name: 'Templates and routes', completed: true, note: 'add notes...' },
        { name: 'Filters and Services', completed: false, note: 'add notes...' },
        { name: 'Get started with Node/ExpressJS', completed: false, note: 'add notes...' },
        { name: 'Setup MongoDB database', completed: false, note: 'add notes...' },
        { name: 'Be awesome!', completed: false, note: 'add notes...' },
      ];
    })
    //---------------
    // Controllers
    //---------------
    .controller('TodoController', ['$scope', 'Todos', function ($scope, Todos) {
      $scope.todos = Todos;
    }])
    .controller('TodoDetailCtrl', ['$scope', '$routeParams', 'Todos', function ($scope, $routeParams, Todos) {
      $scope.todo = Todos[$routeParams.id];
    }])
    //---------------
    // Routes
    //---------------
    .config(['$routeProvider', function ($routeProvider) {
      $routeProvider
        .when('/', {
          templateUrl: '/todos.html',
          controller: 'TodoController'
        })
        .when('/:id', {
          templateUrl: '/todoDetails.html',
          controller: 'TodoDetailCtrl'
       });
    }]);
</script>

</body>
</html>

Thanks for reading this far.

Angular 8 Node & Express JS File Upload

Angular 8 Node & Express JS File Upload

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

We are also going to take the help of Node.js to create the backend server for Image or File uploading demo. Initially, we’ll set up an Angular 8 web app from scratch using Angular CLI. You must have Node.js and Angular CLI installed in your system.

We’ll create the local server using Node.js and multer middleware. Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. Once we are done setting up front-end and backend for our File uploading demo then, we’ll understand step by step how to configure file uploading in Angular 8 app using Node server.

Prerequisite

In order to show you Angular 8 File upload demo, you must have Node.js and Angular CLI installed in your system. If not then check out this tutorial: Set up Node JS

Run following command to install Angular CLI:

npm install @angular/cli -g

Install Angular 8 App

Run command to install Angular 8 project:

ng new angular-node-file-upload

# ? Would you like to add Angular routing? No
# ? Which stylesheet format would you like to use? CSS
cd angular-node-file-upload

Show Alert Messages When File Uploaded

We are going to install and configure ngx-toastr an NPM package which helps in showing the alert message when the file is uploaded on the node server.

npm install ngx-toastr --save

The ngx-toastr NPM module requires @angular/animations dependency:

npm install @angular/animations --save

Then, add the ngx-toastr CSS in angular.json file:

"styles": [
    "src/styles.css",
    "node_modules/ngx-toastr/toastr.css"
]

Import BrowserAnimationsModule and ToastrModule in app.module.ts file:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
 
@NgModule({
  imports: [
    CommonModule,
    BrowserAnimationsModule, // required animations module
    ToastrModule.forRoot() // ToastrModule added
  ]
})

export class AppModule { }

Install & Configure ng-file-upload Directive

In this step, we’ll Install and configure ng-file-upload library in Angular 8 app. Run command to install ng-file-upload library.

npm install ng2-file-upload

Once the ng2-file-upload directive is installed, then import the FileSelectDirective and FormsModule in app.module.ts. We need FormsModule service so that we can create the file uploading component in Angular.

import { FileSelectDirective } from 'ng2-file-upload';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    FileSelectDirective
  ],
  imports: [
    FormsModule
  ]
})

export class AppModule { }

Setting Up Node Backend for File Upload Demo

To upload the file on the server, we need to set up a separate backend. In this tutorial, we will be using Node & Express js to create server locally along with multer, express js, body-parser, and dotenv libraries.

Run command to create backend folder in Angular app’s root directory:

mkdir backend && cd backend

In the next step, create a specific package.json file.

npm init

Run command to install required dependencies:

npm install express cors body-parser multer dotenv --save

In order to get rid from starting the server again and again, install nodemon NPM package. Use –-save-dev along with the npm command to register in the devDependencies array. It will make it available for development purpose only.

npm install nodemon --save-dev

Have a look at final pacakge.json file for file upload demo backend:

{
  "name": "angular-node-file-upload",
  "version": "1.0.0",
  "description": "Angualr 8 file upload demo app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Digamber Rawat",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^8.0.0",
    "express": "^4.17.1",
    "multer": "^1.4.1"
  },
  "devDependencies": {
    "nodemon": "^1.19.1"
  }
}

Create a file by the name of server.js inside backend folder:

Configure Server.js

To configure our backend we need to create a server.js file. In this file we’ll keep our backend server’s settings.

touch server.js

Now, paste the following code in backend > server.js file:

const express = require('express'),
  path = require('path'),
  cors = require('cors'),
  multer = require('multer'),
  bodyParser = require('body-parser');

// File upload settings  
const PATH = './uploads';

let storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, PATH);
  },
  filename: (req, file, cb) => {
    cb(null, file.fieldname + '-' + Date.now())
  }
});

let upload = multer({
  storage: storage
});

// Express settings
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));

app.get('/api', function (req, res) {
  res.end('File catcher');
});

// POST File
app.post('/api/upload', upload.single('image'), function (req, res) {
  if (!req.file) {
    console.log("No file is available!");
    return res.send({
      success: false
    });

  } else {
    console.log('File is available!');
    return res.send({
      success: true
    })
  }
});

// Create PORT
const PORT = process.env.PORT || 8080;
const server = app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Now, while staying in the backend folder run the below command to start the backend server:

nodemon server.js

If everything goes fine then you’ll get the following output:

[nodemon] 1.19.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node server.js`
Connected to port 8080

Create Angular 8 File Upload Component

In this last step, we are going to create a file upload component in Angular 8 app using Express js API.

Get into the app.component.ts file and include the following code:

import { Component, OnInit } from '@angular/core';
import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
import { ToastrService } from 'ngx-toastr';

const URL = 'http://localhost:8080/api/upload';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  public uploader: FileUploader = new FileUploader({
    url: URL,
    itemAlias: 'image'
  });

  constructor(private toastr: ToastrService) { }

  ngOnInit() {
    this.uploader.onAfterAddingFile = (file) => {
      file.withCredentials = false;
    };
    this.uploader.onCompleteItem = (item: any, status: any) => {
      console.log('Uploaded File Details:', item);
      this.toastr.success('File successfully uploaded!');
    };
  }

}

Go to app.component.html file and add the given below code:

<div class="wrapper">
  <h2>Angular Image Upload Demo</h2>

  <div class="file-upload">
    <input type="file" name="image" ng2FileSelect [uploader]="uploader" accept="image/x-png,image/gif,image/jpeg" />
    <button type="button" (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
      Upload
    </button>
  </div>

</div>

Now, It’s time to start the Angular 8 app to check out the File upload demo in the browser. Run the following command:

ng serve --open

Make sure your NODE server must be running to manage the backend.

When you upload the image from front-end you’ll see your image files are saving inside the backend > uploads folder.

Conclusion

In this Angular 8 tutorial, we barely scratched the surface related to file uploading in a Node application. There are various other methods available on the internet through which you can achieve file uploading task quickly. However, this tutorial is suitable for beginners developers. I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others.

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.