06Dec 2020

Introduction to the MEAN(Mongo, ExpressJS, Angular, Node) Stack

A typical web application has several layers;

  • A frontend is what the user sees and interacts with
  • business logic (microservices, REST API), which contains the applications business logic and drives the business rules
  • database backend where we store collected information

The MEAN stack — which stands for Mongo, ExpressJS, Angular, and Node — corresponds to each layer in the solution stack.

  • Angular is a front-end framework. This is what the user will see
  • ExpressJS is a web framework that lets us write web apps and microservices with ease
  • MongoDB is a NoSQL database. We can use it to store data that the app collects
  • Node is a server-side platform that lets us write web apps using JavaScript.

In this article, we’ll briefly explore each of these stacks.

mean-stacks

Basic NodeJS Setup

basic-nodejs-setup

Node (or NodeJS) can be installed quite easily on the three popular OSes (macOS, Linux, and Windows). You can simply go to https://nodejs.org and download the appropriate binary installer for your platform.

For Windows users, the most straightforward way to get Node is probably via the binary installer. All that’s involved is downloading the installer from https://nodejs.org, double click on the installer, and following the prompts.

For you Linux and macOS users, you can use your package managers, e.g., aptitude, yum, arch, or brew, to install NodeJS. The official NodeJS site keeps updated documentation on how to install Node using package managers; here https://nodejs.org/en/download/package-manager/.

Alternatively, you can also use NVM (Node Version Manager) to install Node. The official NVM release supports only macOS and Linux, but Windows users can use the unofficial NVM release for Windows right here https://github.com/coreybutler/nvm-windows.

Before you can install Node via NVM, you first have to install NVM. Installing it is simple; you simply need to download a script file and run it on a terminal, like this

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.36.0/install.sh | bash

Before you run the command above, make sure that the curl utility is installed on your system.

After NVM has successfully installed, you can now install NodeJS, like this.

nvm install node

I trust that with all these options, you should be able to install Node on your system. To test if Node is working correctly, run the following commands on a terminal or a cmd window.

node --version
npm --version

NPM (Node Package Manager), a critical part of the Node ecosystem, is installed with the Node executables.

NodeJS

Node has an interactive environment (REPL), which you can use to play around with Node and JavaScript interactively, but we won’t cover that here. Instead, we’ll go straight to running NodeJS programs.

 The Node platform is comprised of (generally) three things;

  • V8 Engine (from Google).  The same JavaScript engine that powers Chrome and other Chromium browsers like Opera, Vivaldi, Brave, and Microsoft Edge
  • libuv (Unicorn Velociraptor Library). An opensource project is written in multiplatform C. It provides support for I/O based on event-loops. This is responsible for the asynchronous capabilities of NodeJS
  • Core libraries. These are the built-in libraries of Node. Some of the included libraries are used for assertions, DNS lookups, HTTP, file compressions, etc.

To write a Node program, simply create a JavaScript source file, write your program and then run it by passing it as an argument to the node runtime. Create a hello.js file and modify it to match the code listing below.

setTimeout(()=< {
console.log(“Hello”)
}, 2000)
console.log(“World)

Now, let’s run it. On a terminal or cmd window, run the following

node hello.js

If everything went well, you should have seen the following;

World
(pause for two seconds)
Hello

Hello World in NodeJS

Node is different from its server-side counterparts because it’s very close to the metal. It’s different from Java, C#, or PHP in relation to a web server.

Typically, you’d create a serverside app (using Java, C#, or PhP), then you will deploy that app to a server, whether that be Tomcat, Apache, or NGINX. In Node, there is no server. Node itself is the server.

Let’s take the Hello World example of NodeJS. Modify our hello.js example (previously) to match the following code listing.

const http = require('http')

const hostname = '127.0.0.1'
const port = 3000

const server = http.createServer((req, res) => {
 res.statusCode = 200
 res.setHeader('Content-Type', 'text/plain');
 res.end('Hello World')
})

server.listen(port, hostname, () => {
 console.log(`Server running at http://${hostname}:${port}/`)
})

The example above is from the official web page of NodeJS. For quite some time in the past, this code was front and center on the NodeJS site. Let’s walk through the code.

The first line, which reads

const http = require('http')

means we’re getting the http library — this isn’t readily available for Node programs, so we have to require it—and we’re assigning it to a variable (which is also) named http; we can name the variable something else, it’s not required that it’s the same name as the library, but in this case, it is.

By the way, the variable was declared as a const, which effectively makes it a constant — we won’t be able to re-assign any value to it later on.

The second and third lines, which reads

const hostname = '127.0.0.1'
const port = 3000

are merely assigning values to the hostname and port constants.

The next block of code, which reads

const server = http.createServer((req, res) => {
 res.statusCode = 200
 res.setHeader('Content-Type', 'text/plain')
 res.end('Hello World');
})

is the meat and potatoes of this small server-side app. The createServer() function of the http object takes a callback function as an argument, which will be called every time a client (an http agent, like a browser, for example) tries to connect to this server.

The callback function takes two arguments, the HTTP request, and response objects. These arguments are passed by the runtime when the function is called. The three lines inside the createServer() are explained below.

  1. res.statusCode = 200; – We’d like to set the status code of the response object to 200, which means the HTTP request is a success. You can find the listing of HTTP response status codes in the HTTP RFC 7231 (Hypertext transfer protocols, semantics, and content) https://tools.ietf.org/html/rfc7231. You can find a less terse explanation of the codes here https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 
  2. res.setHeader(‘Content-Type’, ‘text/plain’); – We’re setting the header of our response
  3. res.end(‘Hello World’); – Finally, we’re sending textual data via the response object.

The last block of the sample code, which reads

server.listen(port, hostname, () => {
 console.log(`Server running at http://${hostname}:${port}/`)
});

starts the server and listens on port 3000; The third argument of the listen() function isn’t really necessary, but many developers put it anyway because it serves as a status to the server that it’s up and running.

If you want to test this code, edit our hello.js example earlier to match our web server sample code; then, run the following command on a terminal window.

node hello.js

After that, launch a browser and go to http://localhost:3000.

localhost-3000

As you can see, with some minimal codes, we can already build a very basic web service API. The request and response objects of Node is the cornerstone of all web applications.

We can get really clever with these objects and create a very sophisticated web app; but that would mean writing a whole bunch of routing logic for the various HTTP methods (GET, POST, DELETE, PUT, etc.).

 It’s better to do those things with ExpressJS, which is our next stop.

ExpressJS

ExpressJS, or simply Express, is a minimal and flexible web application framework. Many developers use it to organize their apps into an MVC-like structure (Model View Controller) or to create RESTful APIs.

Let’s re-write the hello.js example, which we coded from earlier. Modify it to match the following

const express = require('express')

const port = process.env.PORT || 3000 

let app = express()

app.get('/', (req, res)=> {
 res.send('Hello World');
})

app.listen(port, ()=> {
 console.log('app listening on port ${port}');
})

Let’s walk through the codes. The first line which reads

const express = require('express')

Makes the express library available to our app. Notice that I didn’t import the http library anymore. Express abstracts away a lot of things from our earlier code.

Next line, which reads

app.get('/', (req, res)=> {
 res.send('Hello World');
})

Replaces the createServer() function (from earlier).  The get() function above effectively establishes a route to our app’s document root and responds by sending the “Hello World” text back to whoever makes the request.

Before you can test this code, you need to pull the express library from the NPM repos. From a terminal or a cmd window, type the following.

npm init -y
npm install express --save

The first line initializes the current project directory by creating a package.json file. The second line pulls the express library from NPM repos and saves the entry to the package.json file; this is useful because if you want other programmers to download your project, they don’t have to download all the libraries you used.

They only need to download the source files and the package.json file. To reconstitute the libraries, they only need to execute the following command

npm install

After that, all the libraries you used for the project will be pulled from the repos.

Going back to the project,  now you can run our new program that uses Express.

node hello.js

There’s not much change in the output. It still shows the uninspired “Hello World” screen,

localhost-3000

Establishing Routes

Express has methods that correspond to all HTTP methods; as you might imagine, there is a post(), put(), delete(), as well as a get() method. These verbs make it fairly simple to write RESTful endpoints. The following code, for example

app.get('/employee', (req, res)=> {
 res.send('a list of employees')
})

will respond to an HTTP GET request at http://domain.com/employee, at the moment, we’re simply returning a String that says “a list of employees” — but that’s just a placeholder for an actual list of employees that we’ll build later.

app.get('/employee/:id', (req, res)=> {
 console.log(`Route parameter = ${req.params.id}`)
 res.send('John Doe')
})

The code above will respond to a GET request which accepts a route parameter. It will respond to a request like this http://domain.com/employee/001, where 001 is an employee ID.

Route parameters are specified by placing a colon to the left of the parameter name. To get the value of the route parameter, use the request object’s params property, like this

let bookid = req.params.id;

The DELETE operation is similar to the GET operation; we can write it like this

app.delete('/employee/:id', (req, res)=> {
 res.send('deleted an employee')
})

The code above will respond to an HTTP DELETE request. Like in the previous example, we are accepting a route parameter as part of the path. We will use that parameter to determine which records to delete in the database.

Processing Form Data

In the previous section, we dealt with the READ operation; now, let’s deal with CREATE and UPDATE operations.

app.post('/employee', (req, res)=> {
 res.send('added an employee')
})
app.put('/employee', (req,res)=> {
 res.send('updated an employee')
})

The code listings above will handle POST and PUT requests, which corresponds to RESTful CREATE and UPDATE operations, respectively. The idea is for a frontend app to pass a JSON object that contains an employee record to these endpoints.

The HTTP POST and PUT methods are unlike the GET and DELETE methods; we won’t be able to use the request object to extract the form payload straightforwardly.

If you try to work with form data without the help of any library, you’ll need to write some code that will deal with Streams — we don’t want to do that right now. We want it quick and easy.

To process form data, we will use the body-parser library; so, let’s add it to our project. On a terminal window, run the following command

npm install body-parser --save

To use the library in our project, we have to require it, then initialize it using the following codes.

const bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json())

After that, we can now use it inside the post() method.

app.post('/employee', (req, res)=>{

 let name = req.body.name;
  let email = req.body.email;

 res.json(‘added an employee’);
});

You need to make sure that the form’s HTML input elements are consistent with name and email, as shown in the example code above. The name of the form elements becomes the properties of the request.body object.

CORS

When you’re building microservices, you need to deal with the Single-Origin Policy of HTTP. Since we’re making an API, we have to assume that the consumers won’t be part of our domain. We have to allow Cross-Origin-Resource-Sharing (CORS).

To enable CORS in express, we’ll add the CORS library to our project; like so

npm install cors --save

Then, to use it, we’ll add the following code to server.js

const cors = require(‘cors’);
app.use(cors());

Our Express App

Below is the code listing of our Express app, as it stands right now. Of course, it’s not complete. We have yet to put the database handling codes, but it’s a good idea to take stock of what we currently have.

It’s also a good idea to test it as it stands to make sure that the route works. You can test the routes using the Postman tool (https://www.postman.com/downloads/).

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

const port = process.env.PORT || 3000 

let app = express()

app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json())
app.use(cors());

app.get('/', (req, res)=> {
 res.send('Hello World');
})

app.get('/employee', (req, res)=> {
 res.send('a list of employees')
})

app.get('/employee/:id', (req, res)=> {
 res.send('John Doe');
})

app.post('/employee', (req, res)=> {

 let employeeTemp = {
   name : req.body.name,
   email: req.body.email
  }

  console.log(employeeTemp)

 res.send('added an employee')
})

app.delete('/employee/:id', (req, res)=> {
 res.send('deleted an employee')
})

app.put('/employee', (req,res)=> {
 res.send('updated an employee')
})

app.listen(port, ()=> {
 console.log(`app listening on port ${port}`);
})

MongoDB

mongodb

Let’s move over to the database stack.

MongoDB is a NoSQL database; NoSQL (aka “not only SQL”) databases are different from their relational counterparts with respect to how to they store data.

In NoSQL, you don’t need a schema — well, not strictly, at least. NoSQL databases come in a variety of types based on their data model. Some of the types are document, key-value, wide-column, and graph. MongoDB uses documents as its data type.

If you’re new to NoSQL, the table below shows a comparison of relational database terminologies and their MongoDB counterparts.

SQLMongoDB
databasedatabase
tablecollection
rowdocument
columnfield
indexindex
table joinsembedded documents and linking

A document in MongoDB is essentially a JSON object.

Installing

The simplest way to install MongoDB is to use the binaries, which you can download from the MongoDB website https://www.mongodb.com/try/download/community.

You’d want to install the Community Edition for the purposes of following this tutorial; the Community Edition is offered at no cost while the Enterprise edition is a paid option.

macOS and Linux

  1. Download the installer from the MongoDB website (link is given above)
  2. Choose either Linux or macOS 
  3. Pay attention to the version dropdown list. If you are on Linux, there are a couple of choices on the dropdown list. Do not be so quick to hit the download button. Make sure that your Linux distro is the one selected on the dropdown
  4. Use the tar archiver tool to expand the downloaded installer — tar -xzvfMongoDBb-xxxx-x86_64xxx.tar.gz

Put the expanded folder in a directory where you have read, write, and execute privileges, and then include the folder in your system path.

set MONGO_HOME=/path/where/you/extracted/mongo/installer
set PATH=$MONGO_HOME/bin:$PATH

You can add the codes above to your startup login script (~/.bashrc for Linux and ~/.bash_profile for macOS).

Once the mongo tools are in your path, we can now create the database directory foMongoDBdb. On a terminal window, type the following command

sudo mkdir /data/db
sudo chown `id -u` /data/db

The first line in the example code above creates the database file for MongoDB. WhenMongoDBb starts, it looks for the database files in /data/db folder.

The second line changes the ownership of the /data/db file. The `id -u` evaluates to the currently logged in user (which is us). 

Now, we can start the mongo server. Launch another terminal window, and type

mongod

This is a server process. The terminal won’t come back to the prompt while the server is running. 

To terminate the mongod server, simply press CTRL+C.

Windows

The Windows installer comes either as an msi or zipped file. If you’re more familiar with msi installers, choose that one.

If you download the zip file, then the process of installation will be similar to macOS or Linux (as shown in the previous section). That also means you need to update the system path of Windows to include the MongoDB folder.

I will assume you went for the msi option. Double click the installer, like you would any other Windows app installer; then follow the prompts.

If you accepted the default installation folder, mongo would have been installed at C:\Program Files\MongoDB\Server\x.x\

Let’s create the database directory. Launch a cmd window (with Administrative privileges) and type the following

mkdir c:\data\db
mkdir c:\data\log

Now, start the mongod server

C:\Program Files\MongoDB\Server\x.x\bin\mongod.exe

Using the Mongo Client

Now that the mongo server is up and running, we can connect to it. On Linux or macOS, launch another terminal window and type the following command

mongo

On Windows, launch another cmd window and type the following

C:\Program Files\MongoDB\Server\x.x\bin\mongo.exe

You should see the chevon prompt of MongoDB after launching the mongo client successfully.

chevon-prompt-of-mongodb

Basic Operations

To list all databases, use the command

> show databases
chevon-prompt-of-mongodb

As you can see, I’ve already got a couple of databases in my setup. Yours will probably only show admin, config, and local.

Creating Collections and Documents

If we were using a relational database before we can start adding records, you’d need to do the following;

  1. Create the database, typically using the “CREATE DATABASE …”  command
  2. Then, create the schema of the database
  3. Define some indices
  4. Then you can start adding records

In MongoDB, we don’t need to create a pre-defined schema because mongo databases do not enforce schemas. Schemas are optional; you may or may not want to use them.

If ever you will use schemas, they are not usually defined on the database level but are rather enforced at an application level. So we can just go ahead and create a record (document) right away.

mongo 
> show databases
> use sampledb;
> db.employees.count();
> 0

The first line in the example list above shows how to start the MongoDB client. Assuming you’ve already started the mongod server process, you should see the chevron prompt.

The third line switches to a database named sample.db; but we don’t have a database by that name yet! No worries. In mongo, you can actually switch the context to a database even if it doesn’t exist — it will be created as soon as you add the first document.

The fourth line displays the number of documents on the current database. The db variable is an alias for the currently used database (sampledb); db.employees stand for a collection named employees on the sampledb database — which, of course, doesn’t exist yet.

The count() function shows zero because we haven’t added anything to it yet.

Let’s insert some records.

db.employees.insert({name: “James Gosling”, email: “jgosling@sun.com”})
db.employees.insert({name: “Dennis Ritchie”, email: “dmr@bellabs.com”})

Inserting records in a mongo collection is a straightforward exercise; just use the insert() function and pass the document you want to insert as a JSON object.

In the examples above, we inserted two records in the employees collection — the collection and the database will be created as soon as you add a document.

Run the db.employees.count() command again to check if the inserts succeeded.

db-employees-count-command

It should tell you that there are two records (documents) now.

To display all documents in a collection, we can use the find() function, like this

db.employees.find()
db-employees-find

If you want to prettify the results, you can use the following command

db.employees.find().pretty()
db-employees-find-pretty

If you want to learn more MongoDB commands, you can find them here https://docs.mongodb.com/manual/reference/command/.

We won’t spend too much time in the mongo client since we won’t manage our data from the mongo CLI. We will do them on the application side.

Mongoose 

mongoose

If you’ve worked with an object-relational mapper before like hibernate, ActiveRecord, propel, eloquent, etc., those things do to SQL what mongoose does to mongoDB.

Mongoose is an ODM or an object document mapper.

Talking to MongoDB directly from our Express app is certainly possible, but Mongoose allows us to work with MongoDB with a lot more ease and from a higher abstraction, specifically, an object-oriented abstraction.

Before you can use it, you need to add it to the project via npm

npm install mongoose --save

Using Mongoose from Express

Before anything else, we need to require it, like this

const mongoose = require('mongoose')

Next, let’s connect to the database

mongoose.Promise = global.Promise

const dbURL = "mongodb://localhost/sampledb"
const dbOptions = {useNewUrlParser: true, useUnifiedTopology: true}

mongoose.connect(dbURL, dbOptions)
 .then(res => console.log('connected'))
  .catch(err => console.log(err))

The code above is a boilerplate on how to connect to a locally running MongoDB. It assumes that the database server and the node server are co-located on the same machine.

Performing the Queries

Before you can perform any query against the mongo database, you’ll need to create a mongoose schema and model; like this.

const Schema = mongoose.Schema

let employeeSchema = new Schema({
 id: {type: String}, 
 name: {type: String},
 email: {type:String}
})

let Employee = mongoose.model('Employee', employeeSchema)

List all Documents in a Collection

The model lets us work with the underlying MongoDB collections in an object-oriented way; for example, to list all the documents in the employee collection, we can write the query as follows;

app.get('/employee', (req, res)=> {

 Employee.find((err, data)=> {
   if (err) { res.send(err)}
   else { res.send(data)}
 })
})

The find() function of the model object takes an argument, which is a callback. When the query completes, the runtime invokes the callback and passes two-argument — an error object and the data.

If the error object is null, that means the query is completed without problems, and the data argument contains a list of all documents in the employee collection.

Find and List one Document

To find one document, we can use the findOne() function, as shown below.

app.get('/employee/:id', (req, res)=> {

 let tempid = req.params.id
  let condition = {id: tempid}

 Employee.findOne(condition, (err, data) => {
   res.send(data)
 })
})

Here, we’re using the employee id (which we got from the route parameter) as filter criteria. If the document is found and the query completes without problems, the data argument in the callback function will contain the document we’re looking for.

Create a Document

To create a document, we create an instance of the Employee model; then, we pass the new record as a JSON argument to the Employee constructor.  After creating the object, we call the save() function, as shown below.

app.post('/employee', (req, res)=> {

 let employee = new Employee({
   id: req.body.id,
   name: req.body.name,
   email: req.body.email
  })

 employee.save((err)=> {
   if(err) { res.send(err)}
   else { res.send('added 1 data')}
 })
})

Delete a Document

To delete a document, we use the deleteOne() function. The first argument of the function is an object that represents a criteria on which record to delete.

app.delete('/employee/:id', (req, res)=> {

 let tempid = req.params.id
 Employee.deleteOne({id: tempid}, (err)=> {
   if(err) { res.send(err) }
   else { res.send('updated the record')}
 })
})

In our example above, we get the employee id of the record we want to delete using the URL’s route parameter.

Update a Document

Updating a document is very similar to creating a document; in both cases, we pass a JSON object (which represents the document) in the body of the request object.

app.put('/employee', (req,res)=> {

 let filter = {id: req.body.id}
 let update = {
   name: req.body.name,
   email: req.body.email
  }

 Employee.findOneAndUpdate(filter, update, (err, result)=>{
   if(err) { res.send(err) } 
   else { res.send(result) }
 })
})

A complete Listing of the Server App

We’ve done quite a bit on the server code. If you’ve been following along, it might be difficult to keep things straight. So, here’s the complete listing of server code.

const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser');
const mongoose = require('mongoose')

mongoose.Promise = global.Promise
const Schema = mongoose.Schema

const dbURL = "mongodb://localhost/sampledb"
const dbOptions = {useNewUrlParser: true, useUnifiedTopology: true}

mongoose.connect(dbURL, dbOptions)
 .then(res => console.log('connected'))
  .catch(err => console.log(err))

let employeeSchema = new Schema({
 id: {type: String}, 
 name: {type: String},
 email: {type:String}
})

let Employee = mongoose.model('Employee', employeeSchema)

const port = process.env.PORT || 3000 

let app = express()

app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json())
app.use(cors());

app.get('/', (req, res)=> {
 res.send('Hello World');
})

app.get('/employee', (req, res)=> {

 Employee.find().lean().exec((err, data)=> {
   if (err) { res.send(err)}
   else { res.send(data)}
 })
})

app.get('/employee/:id', (req, res)=> {

 let tempid = req.params.id
  let condition = {id: tempid}

 Employee.findOne(condition, (err, data) => {
   res.send(data)
 })
})

app.post('/employee', (req, res)=> {

 let employee = new Employee({
   id: req.body.id,
   name: req.body.name,
   email: req.body.email
  })

 employee.save((err)=> {
   if(err) { res.send(err)}
   else { res.send('added 1 data')}
 })
})

app.delete('/employee/:id', (req, res)=> {

 let tempid = req.params.id
 Employee.deleteOne({id: tempid}, (err)=> {
   if(err) { res.send(err) }
   else { res.send('updated the record')}
 })
})

app.put('/employee', (req,res)=> {

 let filter = {id: req.body.id}
 let update = {
   name: req.body.name,
   email: req.body.email
  }

 Employee.findOneAndUpdate(filter, update, (err, result)=>{
   if(err) { res.send(err) } 
   else { res.send(result) }
 })
})

app.listen(port, ()=> {
 console.log(`app listening on port ${port}`);
})

Angular

Angular is a SPA framework (single page application). It’s written in TypeScript — and it expects you to write in TypeScript as well. The basic building blocks of Angular are its components that it organizes into modules.

Components are the building blocks of Angular apps. An app is essentially just a collection of components that are arranged either side-by-side or nested in one another. A component has three major parts,

  1.  a class
  2. metadata and
  3. a template

Think of a component as something that contains application logic and that it controls a region of the user interface using HTML templates.

angular

Not all components in Angular contains HTML data. Some do not. Components like services and Pipes do not have HTML data.

Creating an Angular Project

To create an Angular project, we need to get the Angular CLI. From a terminal or cmd window, type the following.

npm install -g @angular/CLI

That should install the Angular command-line utility

Now, we can create a project. On the same terminal window, type the following

ng new employeeui

In the prompt that follows, it will ask you if you “would like to add Angular routing”, press Y for yes (we need routing in this project).

Next, you’ll be asked for your choice of styling. Choose CSS (to keep things simple). The Angular CLI will pull quite a few files from the npm repositories; it could take a while depending on your internet connection.

creating-an-angular-project

When all the files have been downloaded, switch to the newly created Angular project.

cd employeeui

This is the directory where we will run all Angular CLI commands. At this point, the CLI built a boilerplate app for us. To run the app, we need to start the built-in webserver for Angular. From the command line, run the command

ng serve

Then, launch a browser and go to http://localhost:4200.

creating-an-angular-project

When we created the Angular project, a default component was created for us. The output of that default component is what we’re seeing (shown in the screenshot above).

The default component’s files are under the src/app folder (shown below).

creating-an-angular-project

The most important files for us right now are the following;

  • app.component.ts – this contains the definition of our component. It has the model (TypeScript class) and the metadata
  • app.component.html – this contains the HTML template, which is largely responsible for the user interface
  • app.module.ts – The module file is what ties the project together. All component declarations and imports are in here. Whenever we add a component or import a library in the project, we have to update this file.

We won’t access the API endpoints from the components directly; instead, we will create a service that will take care of our HTTP transactions. The diagram below shows a more detailed architecture for our solution.

creating-an-angular-project

Looking at our architecture, we need the following components;

  • Create component – to facilitate the UI for adding an employee
  • Read component – So that we can browse the list of all employees
  • Update component – to facilitate the UI for editing employee information
  • Delete component – to delete a record
  • DataService component – this is where we’ll do all our HTTP transactions

Also, we need to import the HttpModule (for out HTTP requests) and the FormsModule (for creating and updating employee records).

Getting Additional Modules

We have to include two modules that we need for the app; these are;

  • HttpClientModule – we need this to fetch the data from our backend server 
  • FormsModule – we need this because we will use Form elements in the solution

These two modules are not included by default; that’s why you need to declare them. To do that, edit the file app.module.ts and edit it to match the following code snippet

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule} from '@angular/router';
import { HttpClientModule} from '@angular/common/http';
import { FormsModule } from '@angular/forms';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
 declarations: [
   AppComponent
 ],
 imports: [
   BrowserModule,
   AppRoutingModule,
    HttpClientModule,
    FormsModule,
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }

Now, we can use the FormsModule and HttpClientModule from any of our components.

The DataService Component

To generate the DataService component, you can go back to the command line and run the command

ng g s data

The Angular CLI accepts abbreviations for the commands. The command above means generates service. It will generate two files;

  • data.service.spec.ts (the test file)
  • data.service.ts (the class file, this is what we need for now)

If you open data.service.ts, it should look like this

import { Injectable } from '@angular/core';

@Injectable({
 providedIn: 'root'
})
export class DataService {

 constructor() { }
}

We need to import HttpClient and Observable classes. Add the following lines at the top of

data.service.ts.

import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

Next, replace the constructor with this line

constructor(private http:HttpClient) { }

This is a coding pattern in TypeScript which effectively makes the http variable a class member.

Next, let’s create the following methods;

  • getEmployee()
  • addEmployee(employeeData)
  • updateEmployee(employeeData)
  • deleteEmployee(id: String)

These four methods correspond to our CRUD endpoints in the backend server.

The code for the getEmployee() method is as follows

 getEmployee(): Observable<any> {
   return this.http.get('http://localhost:3000/employee');
  }

The method http.get (from the HttpClient class) returns an Observable object; that’s why we typed getEmployee() to return an Observable object.

The code for addEmployee() is as follows

 addEmployee(employeeData) : Observable<any> {
   return this.http.post('http://localhost:3000/employee, employeeData);
  }

For the addEmployee() method, we expect to receive the employeeData parameter; this is a JSON object which will contain the id, name, and email for the employee.

It’s a POST call, so we expect the endpoint to parse the employeeData variable (which will be part of the HTTP request’s body.

Similarly, the updateEmployee() also expects a employeeData argument. The code for the updateEmployee() method is as follows;

updateEmployee(employeeData): Observable<any> {
   return this.http.put('http://localhost:3000/book', employeeData);
  }

 Lastly, the code for the deleteEmployee() method is shown below;

 deleteEmployee(id: String): Observable<any> {
   let url = `http://localhost:3000/employee/${id}`;
    console.log(url);

    return this.http.delete(url);

  }

The ID of the employee to delete is expected as an argument to the deleteEmployee() method. Unlike our POST and PUT requests, we will pass the employee ID as part of the HTTP querystring.

The complete code listing for the DataService class is shown below.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
 providedIn: 'root'
})
export class DataService {

  constructor(private http:HttpClient) { }

 getEmployee(): Observable<any> {
   return this.http.get('http://localhost:3000/employee');
  }

 addEmployee(employeeData): Observable<any>{
   return this.http.post('http://localhost:3000/employee', employeeData);
  }

 updateEmployee(employeeData): Observable<any>{
   return this.http.put('http://localhost:3000/employee', employeeData);
  }




  deleteEmployee(id:String): Observable<any> {

    return this.http.delete(`http://localhost:3000/employee/${id}`);

  }

}

Building the Read, Update, Create and Delete Components

Now that we have our DataService component, we need to build the four UI components for our CRUD app. Like the DataService component, we will use the Angular CLI to generate these components. From the command line, run the following commands

ng g c read
ng g c create
ng g c update
ng g c delete

The project’s file structure should now look like the following

building-the-read-update-create-and-delete-components

The create, delete, read, and update components are under the main app component folder.

Navigation and Styling

For out of the box styling of web pages, it’s hard to beat BootStrap; that’s what we’ll use for our project.

We can download BootStrap via npm and make it local to the project, but let’s take a more straightforward approach. We will simply reference BootStrap via a LINK element in our main HTML file.

Edit the app.component.html file to match the following snippet

<head>
 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>

<div class="container">
 <h1>Our CRUD Project</h1>
 <nav>
   <ul>
     <li>
       <a routerLink="/create">Create a new Employee record</a>
     </li>
     <li>
       <a routerLink="/read">Display all Employees</a>
     </li>
     <li>
       <a routerLink="/update">Update an Employee record</a>
     </li>
     <li>
       <a routerLink="/delete">Delete an Employee record</a>
     </li>
   </ul>
  </nav>

  <router-outlet></router-outlet>

</div>

This sets up basic menu navigation for the main page. The routerLink element is unique to Angular; it uses routerLink instead of an href element to establish links between pages.

Next, we need to bind the CRUD components to the links (routerLink) so that when a link is clicked, the corresponding component shows up on the screen.

Edit app.module.ts to match the following. The changes you need to make are highlighted. These are the codes you need to bind the component to the links.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule} from '@angular/router';
import { HttpClientModule} from '@angular/common/http';
import { FormsModule } from '@angular/forms';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ReadComponent } from './read/read.component';
import { CreateComponent } from './create/create.component';
import { UpdateComponent } from './update/update.component';
import { DeleteComponent } from './delete/delete.component';

@NgModule({
 declarations: [
   AppComponent,
   ReadComponent,
   CreateComponent,
   UpdateComponent,
   DeleteComponent
 ],
 imports: [
   BrowserModule,
   AppRoutingModule,
   HttpClientModule,
   FormsModule,
   RouterModule.forRoot([
     {path: 'create', component: CreateComponent},
     {path: 'read', component: ReadComponent},
     {path: 'update', component: UpdateComponent},
     {path: 'delete', component: DeleteComponent}
   ], {useHash:true}),
 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }

The app should look something like this by now

navigation-and-styling

If you click the links, the corresponding component should show up. You’ll see the placeholder text for each component.

The Create Screen

Open the file create.component.html and edit it to match the following code.

<div class="container">
 <form name="createForm" #form='ngForm' >
   <div class="form-group">
     <label for="empid">ID</label>
     <input type="text" name="id" id="id" ngModel class="form-control">
   </div>
   <div class="form-group">
     <label for="empname">Name</label>
     <input type="text" name="name" id="name" ngModel class="form-control">
   </div>
   <div class="form-group">
     <label for="email">Email</label>
     <input type="text" name="email" id="email" ngModel class="form-control">
   </div>
   <div class="form-group">
     <button (click)="addEmployee(form)" type="button" class="btn btn-primary">Add</button>
   </div>

  </form>

 <pre>{{form.value | json}}</pre>
</div>

It’s a basic form. Most of the codes you see are BootStrap styling. The important part of this code is the form declaration

<form #form='ngForm' >

This line makes it an Angular form. The #form attribute isn’t a simple attribute. You can use that to reference the body of the form from the associated TypeScript class.

The other important line in this code is the declaration of the button

<button (click)=”addEmployee(form)”>Add</button>

The (click) directive makes the button event-aware. When it’s clicked, it will look for a method named addEmployee() in the associated TypeScript class (create.component.ts) and it will pass the form object as an argument to the addBook() method.

Now, edit the file create.component.ts to match the following code

import { Component, OnInit } from '@angular/core';
import {DataService} from '../data.service';
import { NgForm } from '@angular/forms';

@Component({
 selector: 'app-create',
 templateUrl: './create.component.html',
 styleUrls: ['./create.component.css']
})
export class CreateComponent implements OnInit {

  constructor(private dataservice:DataService) { }

 addEmployee(form:NgForm) {
   console.log(form.value.title);
   let formData = {
     "id": form.value.id,
     "name": form.value.name,
     "email": form.value.email
   }
   this.dataservice.addEmployee(formData).subscribe(data=> {
     console.log(data);
   });
 }
 ngOnInit(): void {
 }
}

The addEmployee() method takes in a form object (the one we passed from the HTML form), extracts the id, name, and email. Next, we save it to the formData variable; then, we pass formData to the addEmployee() method of the DataService component.

Remember that the DataService component method returns an Observable object instead of String objects. We will use the subscribe() method of the Observable to extract the String results.

The Read Screen

Edit the file read.component.ts so we can start our work on the ReadComponent class.

This class will fetch the data from our http://localhost:3000/employee endpoint, which effectively performs an Employee.find() command against our sample database and returns the resulting rows as a JSON object.

We will put all our codes on the ngOnInit() callback of the component so that when the component is activated, we perform the fetch.

The code below shows the complete listing for the ReadComponent.

import { Component, OnInit } from '@angular/core';
import {DataService} from '../data.service';

@Component({
 selector: 'app-read',
 templateUrl: './read.component.html',
 styleUrls: ['./read.component.css']
})
export class ReadComponent implements OnInit {

 constructor(private dataservice:DataService) { }
 employees: any[]
 ngOnInit(): void {
   this.dataservice.getEmployee().subscribe(data => {
     this.employees = data;
   });
 }
}

Next, edit the file read.component.html to match the following code listing

<table class="table">
 <thead>
   <tr>
     <td scope="col">ID</td>
     <td scope="col">Name</td>
     <td scope="col">Email</td>
   </tr>
 </thead>
 <tbody>
   <tr *ngFor='let emp of employees'>
     <td>{{emp.id}}</td>
     <td>{{emp.name}}</td>
      <td>{{emp.email}}</td>

   </tr>
 </tbody>
</table>

The <tr> element in the table’s body has an ngFor directive. This is a looping mechanism in Angular. It will go through all the elements in the employees array and then it will assign each element to the emp variable.

The employees array is coming from the ReadComponent class; remember that we declared that array as a member of ReadComponent class which makes it visible in the HTML template file.

We can then get to the id, name, and email properties using the emp variable, like this

  • emp.id – gets the ID field
  • emp.name – gets the name field
  • emp.email – gets the email field

To display the id, name, and email on the table’s cells, we use Angular’s template syntax — the double bracket. 

The Update Screen

The update screen will be similar to our create screen, in fact, we’re going to use exactly the same template. The idea behind the update operation is as follows;

  1. We enter an existing ID on the form
  2. Enter whatever changes you want on the name and email info
  3. Then, click Update

This is not how to handle updates on a real-world application; in there, you’d probably search for the record to update first, then fetch the record, then update the fields — we’re not doing that here.

We’re not doing a lot of validation here as well because we’d like to show the basic tech and the basic workflow only.

Open the template file update.component.html and modify it to match the following code.

<div class="container">
 <form name="createForm" #form='ngForm' >
   <div class="form-group">
     <label for="empid">ID</label>
     <input type="text" name="id" id="id" ngModel class="form-control">
   </div>
   <div class="form-group">
     <label for="empname">Name</label>
     <input type="text" name="name" id="name" ngModel class="form-control">
   </div>
   <div class="form-group">
     <label for="email">Email</label>
     <input type="text" name="email" id="email" ngModel class="form-control">
   </div>
   <div class="form-group">
     <button (click)="updateEmployee(form)" type="button" class="btn btn-primary">Update</button>
    </div>

 </form>
 <pre>{{form.value | json}}</pre>
</div>

The only change in the template is the label of the button. It reads “Update” instead of “Add”. The other change is the name of the method on the click handler; it reads “updateEmployee()” instead of “addEmployee().

Like the Create screen, the important parts of this file is the form declaration

<form #form='ngForm'>

and the button’s (click) handler. It’s the same techniques and principle that we used for the create screen.

Now, edit the file update.component.ts to match the following code.

import { Component, OnInit } from '@angular/core';
import {DataService} from '../data.service';
import { NgForm } from '@angular/forms';

@Component({
 selector: 'app-update',
 templateUrl: './update.component.html',
 styleUrls: ['./update.component.css']
})
export class UpdateComponent implements OnInit {

 constructor(private dataservice:DataService) { }
 updateEmployee(form:NgForm) {
   console.log('called onUpdate');
   let formData = {
     "id": form.value.id,
     "name": form.value.name,
     "email": form.value.email
   }
   this.dataservice.updateEmployee(formData).subscribe(data => {
     console.log(data);
   });
 }
 ngOnInit(): void {
 }
}

Similar to the addEmployee() method of the Create screen, the updateEmployee() takes on a form object as a parameter. Inside the method, we extract the id, name, and email, save it to the variable formData and then send it to the DataService’s updateEmployee() method.

The Delete Screen

Open the file delete.component.html and edit it to match the following code.

<div class="container">
  <form #form='ngForm'>

   <div class="form-group">
     <label for="id">ID</label>
     <input type="text" name="id" id="id" ngModel class="form-control">
   </div>
   <div class="form-group">
     <button (click)="deleteEmployee(form)" type="button" class="btn btn-primary">DELETE</button>
    </div>

  </form>

 <pre>{{form.value | json}}</pre>
</div>

As you can see, the deleteEmployee() method is almost similar to the updateEmployee() and addEmployee() methods; it takes a form object as a parameter, extracts the books value from it, and then calls the corresponding method on the DataService component.

This is the last piece of the solution. You should be able to piece them all up now. To test the solution, make sure that;

  • The mongod server is running
  • Node is up and running
  • ng serve is running

When it comes to the list of web development company IndiaAcodez ranks among the top companies.  We offer all kinds of web design and Mobile app development services to our clients using the latest technologies. We are also a leading digital marketing company providing SEO, SMM, SEM, Inbound marketing services, etc at affordable prices We also offer Branding solutions to our clients. For further information, please contact us.

Looking for a good team
for your next project?

Contact us and we'll give you a preliminary free consultation
on the web & mobile strategy that'd suit your needs best.

Contact Us Now!
Jamsheer K

Jamsheer K

Jamsheer K, is the Tech Lead at Acodez. With his rich and hands-on experience in various technologies, his writing normally comes from his research and experience in mobile & web application development niche.

Get a free quote!

Brief us your requirements & let's connect

Leave a Comment

Your email address will not be published. Required fields are marked *