-
Notifications
You must be signed in to change notification settings - Fork 1
Home
A hands-on tutorial to build a working Angular e-commerce application from scratch on MEAN Stack with deploy & run on Cloud using Heroku. Lean step-by-step how to create a full e-commerce store web site using Angular on MEAN.js stack. Also learn how to deploy the site in Cloud using Heroku. Creating angular app from scratch with MEAN STACK
Title | MEAN JS- Full Course for Beginners |
---|---|
Author | Rupesh Tiwari |
Tags | mean.js, angular, mongo, express, node.js, angular materaial |
Categories | mean.js, fullstack |
Status | Draft |
I will help you to become a full stack MEAN developer step by step. From the very basic you will learn entire stack. This is a hands-on Tutorial to build an working angular app from the scratch on MEAN Stack. Creating Restful Api Server using node.js. Saving Data in MongoDB and deploying our production code to heroku
. You can see each hands-on live in Rupesh Tiwari YouTube Channel
-
MEAN Stack Full Course for Beginners
- Introduction
- Environment Settings
- Creating new Angular App
- Integrating with Angular Material
- Building & Running Angular App
- Adding Registeration Feature
- Creating Restful API server side
- Integrating Restful API to Angular APP
- Configuring Mongoose
- Saving User in Mongo Db
- Login User by verifying user data in Mongo Db
- Debuggin Node.js in Visual Studio Code
- Debugging Angular in Visual Studio Code
- Debugging NodeJs and Angular Together in Visual Studio Code
-
Token Based Authentication
- What are the benefits of using a token-based approach?
- JSON Web Token Generation Process & Authentication process with Google/Facebook/Twitter
- Install passport & jsonwebtoken middleware
- server\controllers\user.controller.js
- server\middleware\passport.js
- server\config\express.js
- server\controllers\auth.controller.js
- server\config\config.js
- .env
- server\routes\auth.route.js
- Client Integration to JWT Authentication
- Improving Network Speed
- Cloud Setup Deploy And Build Sample App In Cloud
- Deploying & Running working Angular App to Cloud
- Architecture in Angular projects
- Cart Feature - Creating Cart Module
- Install Node.js
- Install
Visual Studio Code
- Install
angular cli
by runningnpm i -g @angular/cli
ng new pm-pinch --routing --style scss --prefix pm --skip-install --dry-run
With routing module With sass as default styles With pm as the prefix for all components and directives With Skip installed so it just creats the folder structures With dry run so that it will just create in memory and display it in the console.
ng add @angular/material
- build:
npm run build
- start:
npm start
/* You can add global styles to this file, and also import other style files */
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
@import '~@angular/material/theming';
$custom-typography: mat-typography-config(
$font-family: 'Exo'
);
@include mat-core($custom-typography);
body {
background-color: #f4f3f3;
margin: 0;
app-root * {
font-family: 'Exo', sans-serif;
}
}
.left-spacer{
margin-left: 2%
}
create user interface first.
export interface User {
email:string;
password:string;
repeatPassword?:string;
}
lets update the auth service to create register, login and logout methods.
import { Injectable } from '@angular/core'
import { of, Subject } from 'rxjs'
import { User } from './user'
@Injectable({
providedIn: 'root'
})
export class AuthService {
private user$ = new Subject<User>()
constructor() {}
login(email: string, password: string) {
const loginCredentials = { email, password }
console.log('login credentials', loginCredentials)
return of(loginCredentials)
}
get user() {
return this.user$.asObservable()
}
register(user: any) {
this.user$.next(user)
console.log('registered user successful', user)
return of(user)
}
logout() {
this.user$.next(null)
}
}
header {
width: 100%;
.logo {
background-image: url('../assets/logo.png');
width: 50px;
height: 50px;
background-size: contain;
background-repeat: no-repeat;
}
.mat-icon {
vertical-align: middle;
margin: 0 5px;
}
a {
margin: 1%;
cursor: pointer;
}
.active-link {
background-color: royalblue;
}
}
<header>
<mat-toolbar color="primary">
<mat-toolbar-row>
<a [routerLink]="['/']" class="logo"></a>
<span class="left-spacer"></span>
<a mat-button links side routerLink="/home" routerLinkActive="active-link"
>Home</a
>
<a
mat-button
<header>
<mat-toolbar color="primary">
<mat-toolbar-row>
<a [routerLink]="['/']" class="logo"></a>
<span class="left-spacer"></span>
<a mat-button routerLink="/home" routerLinkActive="active-link"
>Home</a
>
<a
mat-button
routerLink="/products"
routerLinkActive="active-link"
>Products</a
>
<a
mat-button
routerLink="/login"
routerLinkActive="active-link"
*ngIf="!user"
>Login</a
>
<div>
<a
mat-button
*ngIf="user"
[matMenuTriggerFor]="menu"
>
<mat-icon>account_circle</mat-icon>{{ user.fullname }}
</a>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="logout()">logout</button>
</mat-menu>
</div>
</mat-toolbar-row>
</mat-toolbar>
</header>
<router-outlet></router-outlet>
import { Component, OnDestroy } from '@angular/core'
import { AuthService } from './auth.service'
import { Subscription } from 'rxjs'
import { Router } from '@angular/router'
import { User } from './user'
@Component({
selector: 'pm-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnDestroy {
userSubscription: Subscription
user: User
ngOnDestroy(): void {
if (this.userSubscription) {
this.userSubscription.unsubscribe()
}
}
constructor(private authService: AuthService, private router: Router) {
this.userSubscription = this.authService.user.subscribe(
user => (this.user = user)
)
}
logout() {
this.authService.logout()
this.router.navigate(['/'])
}
}
<mat-card class="register-card">
<mat-card-header>
<mat-card-title>Register</mat-card-title>
</mat-card-header>
<mat-card-content>
<form class="register-form">
<table class="full-width" cellspacing="0" [formGroup]="userForm">
<tr>
<td>
<mat-form-field class="full-width">
<input
matInput
placeholder="Fullname"
formControlName="fullname"
name="fullname"
required
/>
</mat-form-field>
</td>
</tr>
<tr>
<td>
<mat-form-field class="full-width">
<input
matInput
placeholder="Email"
formControlName="email"
name="email"
required
/>
<mat-error
*ngIf="email.invalid && email.errors.email"
>Invalid email address</mat-error
>
</mat-form-field>
</td>
</tr>
<tr>
<td>
<mat-form-field class="full-width">
<input
matInput
placeholder="Password"
formControlName="password"
type="password"
name="password"
required
/>
</mat-form-field>
</td>
</tr>
<tr>
<td>
<mat-form-field class="full-width">
<input
matInput
placeholder="Reapet Password"
formControlName="repeatPassword"
type="password"
name="repeatPassword"
required
/>
<mat-error
*ngIf="repeatPassword.invalid && repeatPassword.errors.passwordMatch"
>Password mismatch</mat-error
>
</mat-form-field>
</td>
</tr>
</table>
</form>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button (click)="register()" color="primary">
Register
</button>
<span class="left-spacer"
>Allrady have an account ?
<a [routerLink]="['/login']">login</a> here</span
>
</mat-card-actions>
</mat-card>
.register-card {
max-width: 800px;
min-width: 150px;
margin: 10% auto;
}
.full-width {
width: 100%;
}
import { FormControl, Validators } from '@angular/forms'
import { Component, OnInit } from '@angular/core'
import { FormGroup } from '@angular/forms'
import { Router } from '@angular/router'
import { AuthService } from '../auth.service'
@Component({
selector: 'pm-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit {
userForm = new FormGroup({
fullname: new FormControl('', [Validators.required]),
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required]),
repeatPassword: new FormControl('', [
Validators.required,
this.passwordsMatchValidator
])
})
constructor(private router: Router, private authService: AuthService) {
console.log('userform', this.userForm)
}
passwordsMatchValidator(control: FormControl) {
let password = control.root.get('password')
return password && control.value !== password.value
? {
passwordMatch: true
}
: null
}
register() {
if (!this.userForm.valid) return
const user = this.userForm.getRawValue()
this.authService
.register(user)
.subscribe(s => this.router.navigate(['']))
}
get fullname(): any {
return this.userForm.get('fullname')
}
get email(): any {
return this.userForm.get('email')
}
get password(): any {
return this.userForm.get('password')
}
get repeatPassword(): any {
return this.userForm.get('repeatPassword')
}
ngOnInit() {}
}
install below npm packages:
npm i -S express dotenv body-parser cors helmet morgan express-async-handler
npm i -D concurrently nodemon
require('dotenv').config()
const envVars = process.env
module.exports = {
port: envVars.PORT,
env: envVars.NODE_ENV
}
const express = require('express');
const path = require('path');
const config = require('./config');
const logger = require('morgan');
const bodyParser = require('body-parser');
const cors = require('cors');
const helmet = require('helmet');
const routes = require('../routes/index.route');
const app = express();
// logger only in dev env
if (config.env === 'development') {
app.use(logger('dev'));
}
const distDir = path.join(__dirname, '../../dist');
// dist folder hosting
app.use(express.static(distDir));
// api body parsing
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// secure apps by setting various HTTP headers
app.use(helmet());
// enable CORS - Cross Origin Resource Sharing
app.use(cors());
// API router
app.use('/api/', routes);
// index html serving
app.get('*', (req, res) => res.sendFile(path.join(distDir, 'index.html')));
module.exports = app;
const express = require('express')
const userController = require('../controllers/user.controller')
const asyncHandler = require('express-async-handler')
const router = express.Router()
router.post('/register', asyncHandler(insert))
async function insert(req, res, next) {
console.log(`registering user`, req.body)
let user = await userController.insert(req.body)
res.json(user)
}
module.exports = router
const express = require('express')
const authRoutes = require('./auth.route')
const router = express.Router()
router.use('/auth', authRoutes)
module.exports = router
module.exports = {
insert
}
users = []
async function insert(user) {
return users.push(user)
}
const app = require('./config/express')
const config = require('./config/config')
// listen to port
app.listen(config.port, () => {
console.info(`server started on port ${config.port} (${config.env})`)
})
add below filter
# environment
.env
NODE_ENV=development
PORT = 4050
.env file will have all of the environment variables which will vary per environment like prod, qa , dev , stage, uat servers.
Therefore, do not checkin this file in github. Rather create .env.sample
file to checkin in github for just helping others to get the .env file locally by running simple copy command cp .env.sample .env
this way locally anyone can get .env
file.
update start:server script and create kill all node processes script.
"scripts": {
"ng": "ng",
"start:server": "concurrently -c \"yellow.bold,green.bold\" -n \"SERVER,CLIENT\" \"npm run server:watch\" \"npm run build:watch\"",
"server:watch": "nodemon server",
"serve": "ng serve -o --port 4030",
"build": "ng build",
"build:watch": "ng build --watch",
"build:prod": "npm run build -- --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"stop": "taskkill /f /im node.exe"
}
create a sample env file to check in github for reuse. update the read me for how to create .env file from .env.sample
# after cloning repository run below command from root folder
cp .env.sample .env
now you can use postman and register user by posting data.
Integrating Restfull API with Auth service
import { Injectable } from '@angular/core'
import { of, Subject, throwError } from 'rxjs'
import { User } from './user'
import { HttpClient } from '@angular/common/http'
import { switchMap, catchError } from 'rxjs/operators'
@Injectable({
providedIn: 'root'
})
export class AuthService {
private user$ = new Subject<User>()
constructor(private http: HttpClient) {}
apiUrl = '/api/auth/'
login(email: string, password: string) {
const loginCredentials = { email, password }
console.log('login credentials', loginCredentials)
return of(loginCredentials)
}
logout() {
// remove user from suject
this.setUser(null)
console.log('user did logout successfull')
}
get user() {
return this.user$.asObservable()
}
register(user: any) {
return this.http.post<User>(`${this.apiUrl}register`, user).pipe(
switchMap(savedUser => {
this.user$.next(savedUser)
console.log('registered user successful', savedUser)
return of(savedUser)
}),
catchError(err => {
return throwError('Registration failed please contact admin')
})
)
}
private setUser(user) {
this.user$.next(user)
}
}
import httpclient here
import { HttpClientModule } from '@angular/common/http'
imports: [HttpClientModule]
now if you run npm run start:server you can register user successfully.
However , if you want to run ng serve and register user then you will get 404 Not Found
error
In order to solve this you need to add proxy to angular.json
file.
go to serve.options and add "proxyConfig": "proxy.conf.json"
"serve": {
...
"options": {
"browserTarget": "product-mart:build",
"proxyConfig": "proxy.conf.json"
},
...
next create proxy.conf.json
file.
{
"/api": {
"target": "http://localhost:4050",
"secure": false
}
}
kill all process and then start server in watch mode and serve client.
- MongoDb Installation for windows: https://www.mongodb.com/download-center/community?jmp=docs msi to install in windows machine
- Mongoose MPM: https://www.npmjs.com/package/mongoose npm library to connect to mongo db from node.js
- Install robo 3T https://robomongo.org/download This is an editor to view db collections.
npm i -S mongoose
Install mongo db in your machine. If you are using mac os then follow this steps
add mongo object
require("dotenv").config();
const envVars = process.env;
module.exports = {
port: envVars.PORT,
env: envVars.NODE_ENV,
mongo: {
uri: envVars.MONGODB_URI,
port: envVars.MONGO_PORT,
isDebug: envVars.MONGOOSE_DEBUG
}
};
const mongoose = require('mongoose');
const util = require('util');
const debug = require('debug')('express-mongoose-es6-rest-api:index');
const config = require('./config');
const mongoUri = config.mongo.uri;
mongoose.connect(mongoUri, { keepAlive: 1, useNewUrlParser: true });
const db = mongoose.connection;
db.once('open', ()=>{
console.log(`connected to database: ${mongoUri}`);
})
db.on('error', () => {
throw new Error(`unable to connect to database: ${mongoUri}`);
});
// print mongoose logs in dev env
if (config.mongo.isDebug) {
mongoose.set('debug', (collectionName, method, query, doc) => {
debug(`${collectionName}.${method}`, util.inspect(query, false, 20), doc);
});
}
module.exports = db;
NODE_ENV=development
PORT = 4050
MONGODB_URI=mongodb://localhost/productmart
MONGOOSE_DEBUG:true
initialize mongoose before listening to port
// initialize mongo
require('./config/mongoose')
Now run npm run server:watch and see mongoose connected successfully. see below message :
server started on port 4050 (development)
connected to database: mongodb://localhost/productmart
Now we will create model and save user into mongo db.
npm i -S bcrypt
we will create hashed password before saving to db.
// user.ts
export interface User {
email: string
fullname: string
password: string
}
const mongoose = require('mongoose')
const UserSchema = new mongoose.Schema(
{
fullname: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true,
// Regexp to validate emails with more strict rules as added in tests/users.js which also conforms mostly with RFC2822 guide lines
match: [
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
'Please enter a valid email'
]
},
hashedPassword: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
roles: [
{
type: String
}
]
},
{
versionKey: false
}
)
module.exports = mongoose.model('User', UserSchema)
const bcrypt = require('bcrypt')
const User = require('../models/user.model')
async function insert(user) {
// make a mogoose db call to save user in db
console.log(`saving user to db`, user)
// go to https://www.npmjs.com/package/bcrypt#to-hash-a-password-1
// and learn about hashsync
// the salt to be used to hash the password.
user.hashedPassword = bcrypt.hashSync(user.password, 10)
delete user.password
console.log(`user to save in db`, user)
return await new User(user).save()
}
module.exports = {
insert
}
Now you can go to http://localhost:4050/register
page and register yourself check mongo db you will have data saved.
add login method in auth service
login(email: string, password: string) {
const loginCredentials = { email, password };
console.log('login credentials', loginCredentials);
// return of(loginCredentials);
return this.http.post<User>(`${this.apiUrl}login`, loginCredentials).pipe(
switchMap(savedUser => {
this.user$.next(savedUser);
console.log('find user successful', savedUser);
return of(savedUser);
}),
catchError(err => {
console.log('find user fail', err);
return throwError(
'Your login details could not be verified. Please try again.'
);
})
);
}
add login route
router.post('/login', asyncHandler(getUser))
async function getUser(req, res, next) {
const user = req.body
console.log(`searching user`, user)
const savedUser = await userController.getUserByEmail(
user.email,
user.password
)
res.json(savedUser)
}
update user controller to have getuserbyemail method.
async function getUserByEmail(email, password) {
let user = await User.findOne({ email })
if (isUserValid(user, password, user.hashedPassword)) {
user = user.toObject()
delete user.hashedPassword
return user
} else {
return null
}
}
function isUserValid(user, password, hashedPassword) {
return user && bcrypt.compareSync(password, hashedPassword)
}
Update login.component.ts to show error
import { Component, OnInit } from '@angular/core'
import { Router } from '@angular/router'
import { AuthService } from '../auth.service'
@Component({
selector: 'pm-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
email: string
password: string
error: string
constructor(private router: Router, private authService: AuthService) {}
ngOnInit() {}
login() {
this.error = ''
this.authService.login(this.email, this.password).subscribe(
s => this.router.navigate(['/']),
e => {
console.log('show ui error', e)
this.error = e
}
)
}
}
Update logincomponent.html to add this extra row to show error.
...
<tr>
<td colspan="2">
<mat-error *ngIf="error">{{error}}</mat-error>
</td>
</tr>
...
Now if you login with correct credential you will be navigated to home page. Or if you did wrong credentials then you will see error.
Now I will demonstrate how can you debug serverside code in vscode live.
Go to debug tab and select add configuration for node.js
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Server",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "npm",
"runtimeArgs": ["run-script", "debug"],
"port": 9229
}
]
}
add "debug": "node --nolazy --inspect-brk=9229 server"
in your package.json/scripts
scripts: {
...
"debug": "node --nolazy --inspect-brk=9229 server"
...
}
Now hit F5 button and you should be able to run your server in debug mode.
Lets debug client side app now.
inside launch.json add below object
{
"name": "Launch Angular",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: serve",
"url": "http://localhost:4200/",
"webRoot": "${workspaceFolder}"
}
add below object in task.json
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "serve",
"isBackground": true,
"presentation": {
"focus": true,
"panel": "dedicated"
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": {
"owner": "typescript",
"source": "ts",
"applyTo": "closedDocuments",
"fileLocation": ["relative", "${cwd}"],
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "Compiled |Failed to compile."
}
}
}
}
]
}
Here is your final scripts in package.json
"scripts": {
"ng": "ng",
"start:server": "concurrently -c \"yellow.bold,green.bold\" -n \"SERVER,CLIENT\" \"npm run server:watch\" \"npm run build:watch\"",
"server:watch": "nodemon server",
"serve": "ng serve",
"build": "ng build",
"build:watch": "ng build --watch",
"build:prod": "npm run build -- --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"stop": "taskkill /f /im node.exe",
"debug": "node --nolazy --inspect-brk=9229 server"
Now you can launch your angular app in debug mode.
Lets debug both client and server together.
inside launch.json add below object
"compounds": [
{
"name": "Do Em Both",
"configurations": ["Debug Server", "Launch Angular"]
}
Here is the final launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Server",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "npm",
"runtimeArgs": ["run-script", "debug"],
"port": 9229
},
{
"name": "Launch Angular",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: serve",
"url": "http://localhost:4200/",
"webRoot": "${workspaceFolder}"
}
],
"compounds": [
{
"name": "Do Em Both",
"configurations": ["Debug Server", "Launch Angular"]
}
]
}
The general concept behind a token-based authentication system is simple. Allow users to enter their username and password in order to obtain a token which allows them to fetch a specific resource - without using their username and password. Once their token has been obtained, the user can offer the token - which offers access to a specific resource for a time period - to the remote site. JWT Other advantages are Token-Based Authentication, relies on a signed token that is sent to the server on each request.
Cross-domain / CORS: cookies + CORS don't play well across different domains. A token-based approach allows you to make AJAX calls to any server, on any domain because you use an HTTP header to transmit the user information.
Stateless (a.k.a. Server side scalability): there is no need to keep a session store, the token is a self-contained entity that conveys all the user information. The rest of the state lives in cookies or local storage on the client side.
CDN: you can serve all the assets of your app from a CDN (e.g. javascript, HTML, images, etc.), and your server side is just the API.
Decoupling: you are not tied to any particular authentication scheme. The token might be generated anywhere, hence your API can be called from anywhere with a single way of authenticating those calls.
Mobile ready: when you start working on a native platform (iOS, Android, Windows 8, etc.) cookies are not ideal when consuming a token-based approach simplifies this a lot.
CSRF: since you are not relying on cookies, you don't need to protect against cross site requests (e.g. it would not be possible to sib your site, generate a POST request and re-use the existing authentication cookie because there will be none).
Performance: we are not presenting any hard perf benchmarks here, but a network roundtrip (e.g. finding a session on database) is likely to take more time than calculating an HMACSHA256 to validate a token and parsing its contents.
JWTs allow for the token to be cryptographically signed, meaning that you can guarantee that the token has not been tampered with. There is also provision for them to be encrypted, meaning that without the encryption key the token can not even be decoded.
First install below packages to support JWT Authentication.
npm i -S passport passport-jwt passport-local jsonwebtoken
-
passport
: Passport's sole purpose is to authenticate requests, which it does through an extensible set of plugins known as strategies likepassport-jwt, passport-local
-
jsonwebtoken
: Creates JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties.
https://www.npmjs.com/package/passport http://www.passportjs.org/docs/ (read "Verify Callback" header ) JWT
Find User By ID
add getUserById
function and rename existing function getUserByEmail
to getUserByEmailPassword
also update the
server\routes\auth.route.js
to use getUserByEmailPassword
.
async function getUserById(id) {
let user = await User.findById(id)
if (user) {
user = user.toObject()
delete user.hashedPassword
return user
} else {
return null
}
}
module.exports = {
insert,
getUserByEmail,
getUserById
}
Complete user.controller.js file looks like below
const bcrypt = require('bcrypt')
const User = require('../models/user.model')
async function insert(user) {
// make a mogoose db call to save user in db
console.log(`saving user to db`, user)
// go to https://www.npmjs.com/package/bcrypt#to-hash-a-password-1
// and learn about hashsync
// the salt to be used to hash the password.
user.hashedPassword = bcrypt.hashSync(user.password, 10)
delete user.password
console.log(`user to save in db`, user)
return await new User(user).save()
}
async function getUserByEmailPassword(email, password) {
let user = await User.findOne({ email })
if (isUserValid(user, password, user.hashedPassword)) {
user = user.toObject()
delete user.hashedPassword
return user
} else {
return null
}
}
async function getUserById(id) {
let user = await User.findById(id)
if (user) {
user = user.toObject()
delete user.hashedPassword
return user
} else {
return null
}
}
function isUserValid(user, password, hashedPassword) {
return user && bcrypt.compareSync(password, hashedPassword)
}
module.exports = {
insert,
getUserByEmailPassword,
getUserById
}
Create Passport Middleware & Register it in App
- For local login it will search user by email id
- For JWT login it will parse the Token and get the User Id and search user by user id.
const passport = require('passport')
const LocalStrategy = require('passport-local')
const JwtStrategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt
const bcrypt = require('bcrypt')
const config = require('./config')
const userController = require('../controllers/user.controller')
const localLogin = new LocalStrategy(
{
usernameField: 'email',
passwordField:'password',
},
async (email, password, done) => {
const user = await userController.getUserByEmailPassword(
email,
password
)
return user
? done(null, user)
: done(null, false, {
error:
'Your login details could not be verified. Please try again.'
})
}
)
const jwtLogin = new JwtStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.jwtSecret
},
async (payload, done) => {
const user = await userController.getUserById(payload._id)
return user ? done(null, user) : done(null, false)
}
)
module.exports = passport.use(localLogin).use(jwtLogin)
Add the passport middleware in our app.
// authentication
app.use(passport.initialize())
The complete express.js file looks like this
const express = require('express')
const path = require('path')
const logger = require('morgan')
const bodyParser = require('body-parser')
const cors = require('cors')
const helmet = require('helmet')
const routes = require('../routes')
const config = require('./config')
const passport = require('../middleware/passport')
// get app
const app = express()
// logger
if (config.env === 'development') {
app.use(logger('dev'))
}
// get dist folder
const distDir = path.join(__dirname, '../../dist')
// use dist flder as hosting folder by express
app.use(express.static(distDir))
// parsing from api
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
// secure apps http
app.use(helmet())
// allow cors
app.use(cors())
// authentication
app.use(passport.initialize())
// api router localhost:4050/api
app.use('/api/', routes)
// serve the index.html
app.get('*', (req, res) => res.sendFile(path.join(distDir, 'index.html')))
module.exports = app
Generate JSON Web Token
Now we will add generateToken method in auth controller this token we will return to the client.
jwt = require('jsonwebtoken')
const config = require('../config/config')
module.exports = { generateToken }
function generateToken(user) {
const payload = JSON.stringify(user)
return jwt.sign(payload, config.jwtSecret, { expiresIn: '1h' })
// do not put secret in source code
}
Add jwtSecret in config
require('dotenv').config()
const envVars = process.env
module.exports = {
port: envVars.PORT,
env: envVars.NODE_ENV,
jwtSecret: envVars.JWT_SECRET,
mongo: {
uri: envVars.MONGODB_URI,
port: envVars.MONGO_PORT,
isDebug: envVars.MONGOOSE_DEBUG
}
}
Note: It’s important that your secret is kept safe: only the originating server should know what it is. It’s best practice to set the secret as an environment variable, and not have it in the source code, especially if your code is stored in version control somewhere.
Add tje jwt secret value in .env file
NODE_ENV = development
PORT = 4050
MONGODB_URI=mongodb://localhost/productmart
MONGOOSE_DEBUG:true
JWT_SECRET=9f31e57b-4a4d-436f-a844-d9236837b2f6
Update Auth Route
- Updating auth route to have a
findme
method to populate the user based on the token by adding passport middlewares. - Creating APIs
register, login and findme
and for each one we will add a login middleware to return bothuser & token
const express = require('express')
const asyncHandler = require('express-async-handler')
const userController = require('../controllers/user.controller')
const passport = require('../middleware/passport')
const authController = require('../controllers/auth.controller')
const router = express.Router()
// localhost:4050/api/auth/register
router.post('/register', asyncHandler(register), login)
// localhost:4050/api/auth/login
router.post('/login', passport.authenticate('local', { session: false }), login)
// localhost:4050/api/auth/findme
router.get('/findme', passport.authenticate('jwt', { session: false }), login)
async function register(req, res, next) {
const user = req.body
console.log(`registering user`, user)
req.user = await userController.insert(user)
next()
}
function login(req, res) {
const user = req.user
const token = authController.generateToken(user)
res.json({
user,
token
})
}
module.exports = router
Now if you register a user from UI then you will see below in the network tab of chrome.
{
"user": {
"roles": [],
"_id": "5cd6cabf0fa512398cd8fbe1",
"fullname": "asfsd",
"email": "[email protected]",
"hashedPassword": "$2b$10$K3lsayWxuriTDF5/MB5s8ejPMwD/t8M8CZYlZoYpOHddL/y8EZ4DK",
"createdAt": "2019-05-11T13:14:39.770Z"
},
"token": "eyJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6W10sIl9pZCI6IjVjZDZjYWJmMGZhNTEyMzk4Y2Q4ZmJlMSIsImZ1bGxuYW1lIjoiYXNmc2QiLCJlbWFpbCI6ImFzZGZAZ21haWwuY29tIiwiaGFzaGVkUGFzc3dvcmQiOiIkMmIkMTAkSzNsc2F5V3h1cmlUREY1L01CNXM4ZWpQTXdEL3Q4TThDWllsWm9ZcE9IZGRML3k4RVo0REsiLCJjcmVhdGVkQXQiOiIyMDE5LTA1LTExVDEzOjE0OjM5Ljc3MFoifQ.VenrIYnBPMZJ23q8kQIRFLzBk7gouK_oogRssZIKI_8"
}
We will integrate JWT serverside apis with angular code base.
In client we have to save the web token in local storage therefore we will create a service.
run below script
ng g s tokenStorage
This will create token.storage.service.ts
import { Injectable } from '@angular/core';
const TOKEN_KEY = 'ProductMart.AuthToken';
@Injectable({providedIn:'root'})c
export class TokenStorage {
constructor() {}
signOut() {
window.localStorage.removeItem(TOKEN_KEY);
window.localStorage.clear();
}
saveToken(token: string) {
if (!token) {
return;
}
window.localStorage.removeItem(TOKEN_KEY);
window.localStorage.setItem(TOKEN_KEY, token);
}
public getToken(): string {
return localStorage.getItem(TOKEN_KEY);
}
}
Save Json Web Token After Register
import { TokenStorage } from './token.storage.service';
constructor(private http: HttpClient, private tokenStorage: TokenStorage) {}
register(userToSave: any) {
return this.http.post<any>(`${this.apiUrl}register`, userToSave).pipe(
switchMap(({ user, token }) => {
this.user$.next(user);
this.tokenStorage.saveToken(token);
console.log('register user successful', user);
return of(user);
}),
catchError(err => {
return throwError('Registration failed please contact admin');
})
);
}
Now if you register an user from website then you can check the chrome application localstorage and you will find your key and token stored their.
However now if you refresh your page user is no more available in our angular app.
Lets now create findme
method that we will call every time when app.component
is refreshed.
Fetch User once logged in after page refresh also
adding findme
method in authservice
to get the user info from token.
findMe() {
const token = this.tokenStorage.getToken();
if (!token) {
return EMPTY;
}
return this.http.get<any>('/api/auth/findme').pipe(
switchMap(({ user }) => {
this.setUser(user);
console.log('Find User Success', user);
return of(user);
}),
catchError(err => {
return throwError('Find User Failed');
})
);
}
Now lets call findme
function in app.component
First we will implement onInit interface call find me and then subscribe to the user updated.
ngOnInit() {
// find the user if already logged in
this.allSubscriptions.push(
this.authService.findMe().subscribe(({ user }) => (this.user = user))
);
// update this.user after login/register/logout
this.allSubscriptions.push(
this.authService.user.subscribe(user => (this.user = user))
);
}
This is the complete file for the app.component
import { Component, OnDestroy, OnInit } from '@angular/core'
import { AuthService } from './auth.service'
import { Router } from '@angular/router'
import { User } from './user'
import { Subscription } from 'rxjs'
@Component({
selector: 'pm-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnDestroy, OnInit {
user: User
allSubscriptions: Subscription[] = []
constructor(private authService: AuthService, private router: Router) {}
ngOnInit() {
// find the user if already logged in
this.allSubscriptions.push(this.authService.findMe().subscribe())
// update this.user after login/register/logout
this.allSubscriptions.push(
this.authService.user.subscribe(user => (this.user = user))
)
}
logout() {
this.authService.logout()
this.router.navigate(['/'])
}
ngOnDestroy(): void {
this.allSubscriptions.forEach(s => s.unsubscribe())
}
}
Now restart the server and check it out!
Still not working right?
Because we are not passing the token to the server when we are trying to find out the logged in user. The general requirement is if the user is logged in then any request goes to server should have Authorization header
in the request where we put the Bearer ${token}
that goes to server. In order to achieve this in Angular we have httpinterceptors
Lets create our own header.interceptor.ts
now.
Crate JWT Header Interceptor
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest
} from '@angular/common/http'
import { Observable } from 'rxjs'
import { TokenStorage } from '../token.storage.service'
@Injectable()
export class AuthHeaderInterceptor implements HttpInterceptor {
constructor(private tokenStorage: TokenStorage) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
// Clone the request to add the new header
const token = this.tokenStorage.getToken()
const clonedRequest = req.clone({
headers: req.headers.set(
'Authorization',
token ? `Bearer ${token}` : ''
)
})
// Pass the cloned request instead of the original request to the next handle
return next.handle(clonedRequest)
}
}
Http interceptors are added to the request pipeline in the providers section of the app.module.ts file.
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthHeaderInterceptor,
multi: true
}
],
Now lets refresh the client app! Your server is running so now you will see the passport jwtLogin
method is called.
One correction needed in jwtLogin
to put await
const user = await userController.getUserById(payload._id)
const jwtLogin = new JwtStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.jwtSecret
},
async (payload, done) => {
const user = await userController.getUserById(payload._id)
return user ? done(null, user) : done(null, false)
}
)
Now after login if you refresh the page you have logged in ! it Worked!
Once you logout still user is logged in why? When you do logout token is not cleared yet.
got to Authservice
and fix logout src\app\auth.service.ts
logout() {
// remove user from suject
this.tokenStorage.signOut();
this.setUser(null);
console.log('user did logout successfull');
}
Once you login & refresh still user is not persisted why?
Because, we have not yet set the token in localstorage
.
login(email: string, password: string) {
const loginCredentials = { email, password };
console.log('login credentials', loginCredentials);
// return of(loginCredentials);
return this.http.post<any>(`${this.apiUrl}login`, loginCredentials).pipe(
switchMap(({ user, token }) => {
this.tokenStorage.saveToken(token);
this.user$.next(user);
console.log('find user successful', user);
return of(user);
}),
catchError(err => {
console.log('find user fail', err);
return throwError(
'Your login details could not be verified. Please try again.'
);
})
);
}
Install compression npm package this will gzip our http response and hence decrease the payload from kb
to bytes
only :)
npm i -S compression
const compress = require('compression')
app.use(compress())
Now check your network tab again did u see improvement ? I know your answer is yes!
Go Top
We will now deploy our app to cloud. I will use heroku
for that. Please make sure you have account in Heroku.
Angular app needs some some changes before we deploy this app to cloud.
- Create engines
"engines": {
"node": "10.x"
},
- Create a start script that will spawn your server
Once your app is deployed to cloud run your server.
"scripts": { ...
"start": "node server"
}
- Create postinstall script
Once your app will be deployed to cloud you want to run a script that will deploy your angular app to cloud.
"scripts": { ...
"build": "ng build",
"postinstall": "npm run build -- --prod --aot"
}
- Move typescript to dependencies
"dependencies": {
...
"typescript": "~3.2.2"
}
- Create App:
heroku create
- Deploy App:
git push heroku master
- Scale App:
heroku ps:scale web=1
- Open App:
heroku open
- Check Logs :
heroku logs -tail
Mongo Lab
Add required environment variables values on Heroku
Next restart your app : heroku restart
Reopen app: heroku open
See app running live on cloud
Organizing angular project.
- Create Core Module
- Create Shared Module
- Create Feature Module
Shared modules are the module where we put components, pipe, directives and filters that needs to be shared across many features.
components, pipe, directives and filters have to be imported and re-exported in order to use them in different feature.
Run below script to crreate shared module.
ng g m shared
Next move material module under shared module. Because, material module has items that are used across all modules like button
and card
etc.
src\app\shared\shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PmMaterialModule } from './material-module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
@NgModule({
declarations: [],
imports: [CommonModule, PmMaterialModule],
exports: [PmMaterialModule, FormsModule, RouterModule, ReactiveFormsModule]
})
export class SharedModule {}
Import shared module in app.module
and products.module
Core module is a module that can be only imported once in entire app. Therefore, any angular service
which you think needs to be singleton throughout your app consider placing them in core module
Run below script for creating core module.
ng g m core
Next lets import core module in app module.
src\app\app.module.ts
imports: [...CoreModule]
- User Model
- header component
- interceptors
Since User model is used across the app it is good idea to move this into core module.
Lets create Header component inside coremodule. Since it is singleton throughout our app.
Run below script for creating component:
ng g c core/header --skip-tests
File: src\app\core\header\header.component.html
<header>
<mat-toolbar color="primary">
<mat-toolbar-row>
<a class="logo" [routerLink]="['/']"></a>
<span class="left-spacer"></span>
<a mat-button routerLink="/home" routerLinkActive="active-link">Home</a>
<a mat-button routerLink="/products" routerLinkActive="active-link">Products</a>
<a *ngIf="!user" mat-button routerLink="/login" routerLinkActive="active-link">Login</a>
<div>
<a mat-button *ngIf="user" [matMenuTriggerFor]="menu">
<mat-icon>account_circle</mat-icon>{{ user.fullname }}
</a>
<mat-menu #menu="matMenu">
<button (click)="logoutEvent.emit()" mat-menu-item>logout</button>
</mat-menu>
</div>
</mat-toolbar-row>
</mat-toolbar>
</header>
src\app\core\header\header.component.scss
header {
a {
margin: 2px;
}
.active-link {
background-color: #b9c5eb11;
}
.logo {
background-image: url('../../../assets/logo.png');
height: 50px;
width: 50px;
vertical-align: middle;
background-size: contain;
background-repeat: no-repeat;
}
.mat-icon {
vertical-align: middle;
margin: 0 5px;
}
}
src\app\core\header\header.component.ts
import {
Component,
OnInit,
OnDestroy,
Output,
EventEmitter,
Input,
ChangeDetectionStrategy
} from '@angular/core';
import { AuthService } from 'src/app/auth.service';
import { User } from 'src/app/core/user';
@Component({
selector: 'pm-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeaderComponent {
@Input()
user: User;
@Output()
logoutEvent = new EventEmitter<any>();
constructor() {}
}
Import the Header component in core module and dont forget to export it.
Core Module src\app\core\core.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeaderComponent } from './header/header.component';
import { SharedModule } from '../shared/shared.module';
import { RouterModule } from '@angular/router';
@NgModule({
declarations: [HeaderComponent],
imports: [CommonModule, SharedModule, RouterModule],
exports: [HeaderComponent]
})
export class CoreModule {}
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { RegisterComponent } from './register/register.component';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'products'
},
{
path: 'home',
pathMatch: 'full',
component: HomeComponent
},
{
path: 'products',
loadChildren: './products/products.module#ProductsModule'
},
{
path: 'login',
component: LoginComponent
},
{
path: 'register',
component: RegisterComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
AppComponent HTML
src\app\app.component.html
<pm-header (logoutEvent)="logout()" [user]="user$|async"></pm-header>
<router-outlet></router-outlet>
Remove CSS from app component.
Update Appcomponent.ts
import { Component, OnDestroy, OnInit } from '@angular/core';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
import { User } from './core/user';
import { Subscription, Observable } from 'rxjs';
@Component({
selector: 'pm-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
user$: Observable<User>;
subscriptions: Subscription[] = [];
constructor(private authService: AuthService, private router: Router) {}
ngOnInit(): void {
this.user$ = this.authService.user;
this.subscriptions.push(this.authService.findMe().subscribe());
}
logout() {
this.authService.logout();
this.router.navigate(['/']);
}
ngOnDestroy() {
this.subscriptions.forEach(s => s.unsubscribe());
}
}
Care auth module and move login and register components into that feture module.
Run below script to crate auth module
ng g m auth
Import Auth Module in Core Module
Complete code of Core Module src\app\core\core.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeaderComponent } from './header/header.component';
import { SharedModule } from '../shared/shared.module';
import { RouterModule } from '@angular/router';
import { AuthModule } from '../auth/auth.module';
@NgModule({
declarations: [HeaderComponent],
imports: [CommonModule, SharedModule, RouterModule, AuthModule],
exports: [HeaderComponent]
})
export class CoreModule {}
Moving Login
and Register
Folders into the Auth Feature Folder.
src\app\auth\login
src\app\auth\register
Declare Login
and Register
components and import shared module in auth module.
Complete code of AuthModule is:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [LoginComponent, RegisterComponent],
imports: [CommonModule, SharedModule,]
})
export class AuthModule {}
Commit Link: https://github.com/rupeshtiwari/mean-stack-angular-app-from-scratch/commit/d7fc78be3fc4d9e8b729430eadf27e4320b62fe2
Move http interceptors in core module and update the core module providers.
src\app\core\interceptors
Complete code of core module
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeaderComponent } from './header/header.component';
import { SharedModule } from '../shared/shared.module';
import { RouterModule } from '@angular/router';
import { AuthModule } from '../auth/auth.module';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthHeaderInterceptorService } from './interceptors/auth-header-interceptor.service';
@NgModule({
declarations: [HeaderComponent],
imports: [CommonModule, SharedModule, RouterModule, AuthModule],
exports: [HeaderComponent],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthHeaderInterceptorService,
multi: true
}
]
})
export class CoreModule {}
Next remove the interceptor from appmodule complete code of appmodule.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HomeComponent } from './home/home.component';
import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from './shared/shared.module';
import { CoreModule } from './core/core.module';
@NgModule({
declarations: [AppComponent, HomeComponent],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
SharedModule,
HttpClientModule,
CoreModule
],
bootstrap: [AppComponent]
})
export class AppModule {}
Commit Link: https://github.com/rupeshtiwari/mean-stack-angular-app-from-scratch/commit/700f92b00668efc9be4c16760f7ddf58e3028cb9
Add new Cart module with routing
Run below script for cart module
ng g m cart --routing
Now let align the products page and show products in grid and show add to cart button
<mat-grid-list cols="4" rowHeight="700px" [gutterSize]="'10px'">
<mat-grid-tile *ngFor="let product of (products | async)">
<mat-card>
<img mat-card-image src="{{ product.imgUrl }}" alt="{{ product.name }}" />
<mat-card-content>
<h2 class="mat-title">{{ product.name }}</h2>
<h2 class="mat-display-2">$ {{ product.price }}</h2>
</mat-card-content>
<mat-card-actions>
<button mat-button>LIKE</button>
<button mat-button>SHARE</button>
</mat-card-actions>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
We will add a shopping cart service using Behavior Subject
Cart Service will have state management technique using behavior subject.
Run below script to add cart service
ng g s cart/cart
src\app\cart\cart.service.ts
import { Injectable } from '@angular/core'
import { BehaviorSubject } from 'rxjs'
import { initialState, CartState } from './cart-state'
import { map } from 'rxjs/operators'
import { Product } from '../product'
@Injectable({
providedIn: 'root'
})
export class CartService {
cartSubject = new BehaviorSubject<CartState>(initialState)
constructor() {}
get cartState() {
return this.cartSubject.asObservable()
}
get cartCount() {
return this.cartSubject.pipe(map(state => state.products.length))
}
addToCart(product: Product) {
const newState = {
...this.currentState,
products: [].concat(this.currentState.products, product)
}
this.cartSubject.next(newState)
}
get currentState() {
return this.cartSubject.value
}
}
src\app\core\product.ts
export interface Product {
name: string
price: number
}
src\app\cart\state\cart-state.ts
import { Product } from '../product'
export interface CartState {
products: Product[]
}
export const initialState = {
products: []
}
Now we want to show the cart item count. Therfore, lets create an component to show the cart item count in an icon with badge.
ng g c cart/cartItemCount
<mat-icon [matBadge]="cartItemCount$|async" matBadgeColor="warn"
>shopping_cart</mat-icon
>
Injecting Angular Service in Directive
cartItemCount$: Observable<number>;
constructor(private cartService: CartService) {}
ngOnInit() {
this.cartItemCount$ = this.cartService.cartCount;
}
export component to share
src\app\cart\cart.module.ts
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { CartRoutingModule } from './cart-routing.module'
import { SharedModule } from '../shared/shared.module'
import { CartItemCountComponent } from './cart-item-count/cart-item-count.component'
@NgModule({
declarations: [CartItemCountComponent],
exports: [CartItemCountComponent],
imports: [CommonModule, CartRoutingModule, SharedModule]
})
export class CartModule {}
src\app\app.component.html
<div>
<pm-cart-item-count></pm-cart-item-count>
</div>
-
MEAN Stack Full Course for Beginners
- Introduction
- Environment Settings
- Creating new Angular App
- Integrating with Angular Material
- Building & Running Angular App
- Adding Registeration Feature
- Creating Restful API server side
- Integrating Restful API to Angular APP
- Configuring Mongoose
- Saving User in Mongo Db
- Login User by verifying user data in Mongo Db
- Debuggin Node.js in Visual Studio Code
- Debugging Angular in Visual Studio Code
- Debugging NodeJs and Angular Together in Visual Studio Code
-
Token Based Authentication
- What are the benefits of using a token-based approach?
- JSON Web Token Generation Process & Authentication process with Google/Facebook/Twitter
- Install passport & jsonwebtoken middleware
- server\controllers\user.controller.js
- server\middleware\passport.js
- server\config\express.js
- server\controllers\auth.controller.js
- server\config\config.js
- .env
- server\routes\auth.route.js
- Client Integration to JWT Authentication
- Improving Network Speed
- Cloud Setup Deploy And Build Sample App In Cloud
- Deploying & Running working Angular App to Cloud
- Architecture in Angular projects
- Cart Feature - Creating Cart Module