Share
Explore

Lab Workbook: Full-Stack Web Development with MongoDB, Node.js, and Angular

Building Mongo into your Application
Last edited 157 days ago by System Writer

Activity 1: Setting up the Environment


In this activity, you will set up your development environment for full-stack web development with MongoDB, Node.js, and Angular.
Install Node.js and NPM.
Install the Angular CLI.
Install MongoDB.
Create a new Angular project using the CLI.
Create a new Node.js project using npm init.

Activity 2: Building a Simple RESTful API

In this activity, you will build a simple RESTful API using Node.js and Express.js.
Representational State Transfer is a way of doing Web Services.
A RESTful API is of running a method on class on a remote server and getting back the result. RESTful API are a standard to deliver web services via the Service Oriented Architecture way of doing things.
My Video on Service Oriented Architectures:
Install Express.js using npm install express.
Create a new Express.js app.
Define a GET endpoint to retrieve all blog posts.
Define a POST endpoint to create a new blog post.
Test the API using Postman.

Here is an example of what the server.js file might look like:

const express = require('express');
const mongoose = require('mongoose');

const app = express();

mongoose.connect('mongodb://localhost/blog', { useNewUrlParser: true, useUnifiedTopology: true });

const postSchema = new mongoose.Schema({
title: String,
body: String,
date: { type: Date, default: Date.now }
});

const Post = mongoose.model('Post', postSchema);

app.use(express.json());

app.get('/posts', async (req, res) => {
const posts = await Post.find().exec();
res.send(posts);
});

app.post('/posts', async (req, res) => {
const post = new Post(req.body);
await post.save();
res.send(post);
});

app.listen(3000, () => {
console.log('Server started on port 3000');
});



Activity 3: Building the Front-End

In this activity, you will build the front-end of the application using Angular.

Use the Angular CLI to generate a new component for displaying blog posts.
Use the Angular HTTP client to retrieve blog posts from the API.
Display the blog posts in the component.

Here is an example of what the post-list.component.ts file might look like:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css']
})
export class PostListComponent implements OnInit {
posts: any[] = [];

constructor(private http: HttpClient) { }

ngOnInit(): void {
this.loadPosts();
}

loadPosts() {
this.http
.get(`http://localhost:3000/posts`)
.subscribe((posts: any[]) => {
this.posts = posts;
});
}
}


And here is an example of what the post-list.component.html file might look like:

<h2>Blog Posts</h2>

<ul>
<li *ngFor="let post of posts">
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
<small>{{ post.date | date }}</small>
</li>
</ul>

Activity 4: Adding Pagination to the Front-End

In this activity, you will add pagination to the front-end of the application using Angular.

Add a page property to the PostListComponent class.
Add a perPage property to the PostListComponent class.
Modify the loadPosts method to accept page and perPage parameters.
Modify the loadPosts method to use the HttpClient params option to send page and perPage as query parameters.
Modify the post-list.component.html file to display pagination controls.

Here is an example of what the post-list.component.ts file might look like:

import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';

@Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css']
})
export class PostListComponent implements OnInit {
posts: any[] = [];
page = 1;
perPage = 10;

constructor(private http: HttpClient) { }

ngOnInit(): void {
this.loadPosts();
}

loadPosts() {
const params = new HttpParams()
.set('page', this.page.toString())
.set('perPage', this.perPage.toString());

this.http
.get(`http://localhost:3000/posts`, { params })
.subscribe((posts: any[]) => {
this.posts = posts;
});
}

nextPage() {
this.page++;
this.loadPosts();
}

prevPage() {
this.page--;
this.loadPosts();
}
}


And here is an example of what the post-list.component.html file might look like:

<h2>Blog Posts</h2>

<ul>
<li *ngFor="let post of posts">
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
<small>{{ post.date | date }}</small>
</li>
</ul>

<button (click)="prevPage()" [disabled]="page === 1">Previous page</button>
<button (click)="nextPage()">Next page</button>

Activity 5: Adding Validation to the Front-End

In this activity, you will add validation to the front-end of the application using Angular.

Use Angular's built-in validation directives to add client-side validation to the NewPostComponent.
Disable the form submission button if the form is invalid.
Modify the PostListComponent to display error messages if the API returns a validation error.

Here is an example of what the new-post.component.html file might look like:

<h2>New Post</h2>

<form (submit)="createPost()" #postForm="ngForm">
<div>
<label for="title">Title:</label>
<input id="title" name="title" [(ngModel)]="post.title" required>
<div *ngIf="postForm.controls.title.invalid && (postForm.controls.title.dirty || postForm.controls.title.touched)">
<div *ngIf="postForm.controls.title.errors.required">Title is required</div>
</div>
</div>

<div>
<label for="body">Body:</label>
<textarea id="body" name="body" [(ngModel)]="post.body" required></textarea>
<div *ngIf="postForm.controls.body.invalid && (postForm.controls.body.dirty || postForm.controls.body.touched)">
<div *ngIf="postForm.controls.body.errors.required">Body is required</div>
</div>
</div>

<button type="submit" [disabled]="postForm.invalid">Create post</button>
</form>

<div *ngIf="errorMessage">
{{ errorMessage }}
</div>

And here is an example of what the new-post.component.ts file might look like:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';

@Component({
selector: 'app-new-post',
templateUrl: './new-post.component.html',
styleUrls: ['./new-post.component.css']
})
export class NewPostComponent {
post = {
title: '',
body: ''
};

errorMessage = '';

constructor(private http: HttpClient, private router: Router) { }

createPost() {
this.http
.post(`http://localhost:3000/posts`, this.post)
.subscribe(
() => {
this.router.navigate(['/posts']);
},
(error) => {
if (error.status === 422) {
this.errorMessage = error.error.message;
} else {
this.errorMessage = 'An error occurred while creating the post.';
}
}
);
}
}


Activity 6: Adding Authentication to the API


In this activity, we will add authentication to our API using JSON Web Tokens (JWT).
Install the jsonwebtoken package by running npm install jsonwebtoken.
Add the following code to the auth.js file:

const jwt = require('jsonwebtoken');

const SECRET_KEY = 'secret';

function generateToken(user) {
const payload = { sub: user._id };
return jwt.sign(payload, SECRET_KEY);
}

function verifyToken(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ message: 'Authorization header missing' });
}

const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, SECRET_KEY);
req.userId = payload.sub;
next();
} catch (err) {
res.status(401).json({ message: 'Invalid token' });
}
}

module.exports = { generateToken, verifyToken };


In the posts.js file, add the following code to the createPost function:

const { verifyToken } = require('./auth');

router.post('/', verifyToken, async (req, res) => {
const post = new Post({
title: req.body.title,
content: req.body.content,
author: req.userId
});
try {
const newPost = await post.save();
res.status(201).json(newPost);
} catch (err) {
res.status(400).json({ message: err.message });
}
});


Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.