Single page web applications are very interesting and very useful, and that is why they are replacing the old desktop software. Web apps have a lot of advantages over desktop apps. They are so much easier to build and to maintain, flexible and easy to use and they can be used in different devices and systems (Web, Mobile, Desktop…).
JavaScript is the most popular scripting language for web development. It has many frameworks that make web application development much easier like Angular, React and Vue JS for the client side and Node JS, Express JS for the backend side.
Frontend frameworks are very useful, and they help to build high quality scalable web applications. However, they have a very big issue when it comes to SEO, because search engines crawlers find it very difficult to crawl single page web applications. They don't render HTML at the first time when the app runs. Server side rendering will solve this issue by generating HTML files in the server side and rendering them to web crawlers before the app renders.
If you are an Angular developer or you are familiar with it, then feel free to skip this section.
Angular is an open-source single page web application JavaScript framework that was developed by google in 2009. Angular is one of the most popular frontend frameworks. It provides a lot of advanced features in web development such as two way binding, component concept…
A lot of big tech companies uses Angular to build their applications like PayPal, Microsoft, Upwork, Gmail… which means this framework is really powerful.
We all know that single page applications normally executes in the browser, but that is not very helpful to search engine crawlers because it is difficult for them to detect all pages in the app. Angular universal executes in the server side and generates all the pages in our application as static pages, and then all the pages will be sent to the client side.
Angular universal is an open-source solution for server side rendering with angular. It is really helpful because it improves the performance of the application, search engine optimization and will make the application much faster.
You maybe wondering why you should use Angular universal or what are the benefits of this solution, so let’s talk a little bit about the benefits about angular universal.
The rule number one in the internet is if you want your website to be in the top result of google search you need to optimize your website for SEO by putting a good title, a good description, put images with alt attributes and much more. Now, the problem is that single page applications are executed and rendered as JavaScript code and not HTML or CSS and search engine crawlers ignore JavaScript because they need HTML and CSS…
Basically, Angular universal will generate HTML files on the server and then send them back to the client before the real application get rendered. And like that web crawlers will be able to detect each and every web page in our website.
Since angular universal generates HTML files in the server side and render them to the client side before the actual app get rendered, then the user will not wait until the app get rendered to see something in the webpage. The visitor will actually see the content in the website before the actual web application get rendered which is really amazing.
Now that you understand the basics of how server side rendering works with Angular and using Angular universal, let’s jump to the code and see how we can implement this amazing feature in a real angular application. For the purpose of this blog, we will build a simple CRUD application where we will show you exactly step by step how to implement server side rendering. We will use the MEAN Stack to develop this CRUD application. (Angular, Node JS, Express JS, Mongo DB).
First of all, you need to setup a blank Angular project, and to do that run the following command.
ng new ssr-crud-application
Once the installation of the project is done, open the folder of your project in VS code or any code editor you prefer and run the following command to run the Angular server.
ng serve
Now that you are sure your application is working and there was no error during the installation. Let’s add angular universal to our app.
Run the following command which will add all the necessary packages to implement Angular universal.
ng add @nguniversal/express-engine
This command will add Angular universal and its dependencies, and also a node js server which will generate the HTML files and serve them to the browser while running the app. It will add some required packages to get angular universal up and running, and it will also create a file server.ts which holds the node js code. And if you take a look into your package.json file you will find some new commands such as build:ssr and serve:ssr…
This command may actually take some time, so just wait for it to finish, and then run the following command in order to run the server and launch the app.
npm run build:ssr
npm run serve:ssr
After running this two commands, the server of your application will be up and running. Go to your browser and head it to http://localhost:4200/ . Make sure that this is the right port your app is running on.
As you can see, your application is up and running and if you click to see the source code, you will notice that there is content in the source code and it’s not empty, and that is the power of SSR.
Please note that if you are using some variables like document, localStorage… you need to check if this code is running on server or client, because the server will never understand these variables. You can do that using Platform service in Angular.
Now, let’s work on the backend side of our project, we need to setup a new Node JS project. We can do that by opening the terminal and running the following command.
npm init
Now go ahead and open your project in VS Code, and create the server.js file. Before diving into the code and how to create node js server let’s install some important packages.
npm install express body-parser mongodb mongoose
This command will simply install these 4 packages (express, mongodb, mongoose and body parser).
Put the following code in your server.js file in order to create a node js server.
const http = require('http');
const app = require('./app');
const dbConnection = require("./config/db");
const onListening = () => {
const addr = server.address();
const bind = typeof port === 'string' ? 'pipe ' + port : 'port ' + port;
console.log('Listening on ' + bind);
};
const port = 3000;
app.set('port', port);
const server = http.createServer(app);
server.on('listening', onListening);
// Setup Connection to Database
const dbConnection = (server, port) => {
mongoose
.connect(
process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
}, (err) => {
if (err) {
console.log(err);
throw new Error('Error on connecting to database');
} else {
console.log('Connected to database');
server.listen(port);
}
}
)
}
dbConnection(server, port)
Let’s define the schema of our model which will be a book. Create a folder in your node js project and name it models. Under this folder create a .js file and name it Book.js and put the following code in the file.
import mongoose, { Schema } from 'mongoose';
let BookSchema: Schema = new Schema({
name: {
type: String,
required: true
},
author: {
type: String,
required: true
}
}, {
collection: 'books'
})
export default mongoose.model('Book', BookSchema);
Now that we created our model, let’s create the rest API using Express JS. The same way we did with the model, you need to create a folder and name it routes and under this folder create a .js file and name it book-routes.js, and then put the following code.
const express = require("express");
const router = express.Router();
router.post("/", (req, res) => {
const book= new Book(req.body);
book.save().then((book) => {
res.json(book);
}).catch(err => {
res.json(err);
})
});
router.put("/:id", (req, res) => {
Book.updateOne({ _id: ObjectId(req.params.id) }, req.body).then((result) => {
res.json(result);
}, (err) => {
res.json(err);
});
});
router.delete("/:id", (req, res) => {
Book.deleteOne({ _id: ObjectId(req.params.id) }).then((result) => {
res.json(result);
}).catch(err => {
res.json(err);
})
});
router.get("/all", (req, res) => {
Book.find({}).then(books=> {
if (books.length > 0) {
res.json(books.reverse())
} else {
res.status(404).json("No books Found");
}
}).catch(err => {
res.json(err);
})
});
router.get("/:id", (req, res) => {
Book.findOne({ _id: ObjectId(req.params.id) }).then((book) => {
res.json(book);
}).catch(err => {
res.jsn(err);
})
});
module.exports = router;
As you can see, we have created our book api and we implemented all the four main methods of Rest API Get, Post, Put, Delete.
Go ahead and test your API using Postman or Insomnia or any other api tester you prefer.
Make sure you launched the node JS server by running the following command.
node server.js
Our application is a simple CRUD application, so we need to have three main pages in our Angular application which will be add-book, edit-book and list-books components. Run the following command to create these three components.
Your app.module.ts file should look like this.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AddBookComponent } from '../app/components/add-book/add-book.component';
import { EditBookComponent } from '../app/components/edit-book/edit-book.component';
import { ListBooksComponent } from '../app/components/list-books/list-books.component';
@NgModule({
declarations: [
AppComponent,
AddBookComponent,
EditBookComponent,
ListBooksComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
AppRoutingModule,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Your app-routing.module.ts file should be like this
import { NgModule } from '@angular/core';
import { AddBookComponent } from '../app/components/add-book/add-book.component';
import { EditBookComponent } from '../app/components/edit-book/edit-book.component';
import { ListBooksComponent } from '../app/components/list-books/list-books.component';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: '',
redirectTo: '/add-book',
pathMatch: 'full'
},
{
path: 'add-book',
component: AddBookComponent
},
{
path: 'edit-book/:id',
component: EditBookComponent
},
{
path: 'books',
component: ListBooksComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Book model
export interface Book {
_id?: string;
name?: string;
author?: string;
createdAt?: Date;
updatedAt?: Date;
}
We also need to create a service to communicate with our API by running the following command
ng g s book
Your service file should look like this
import { Injectable } from '@angular/core';
import { Book} from './book';
import { Observable, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class BookService {
constructor(
private http: HttpClient
) { }
addBook(book: Book): Observable<any> {
return this.http.post<Book>('/api/create-book', book);
}
getBooks(): Observable<Book[]> {
return this.http.get<Book[]>('/api/get-books');
}
getBook(id): Observable<Book[]> {
return this.http.get<Book[]>('/api/get-book/' + id);
}
updateBook(id, book: Book): Observable<any> {
return this.http.put('/api/update-book/' + id, book);
}
deleteBook(id): Observable<Book[]> {
return this.http.delete<Book[]>('/api/delete-book/' + id);
}
}
The book template will look like this.
<table class="table">
<thead class="table-primary">
<tr>
<th scope="col">#</th>
<th scope="col">Book name</th>
<th scope="col">Author name</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let book of books">
<th scope="row">{{book._id}}</th>
<td>{{book.name}}</td>
<td>{{book.author}}</td>
<td>
<span class="edit" [routerLink]="['/edit-book/', book._id]">Edit</span>
<span class="delete" (click)="removeBook(book, book._id)">Delete</span>
</td>
</tr>
</tbody>
</table>
Display and delete books will look like this.
import { Component, OnInit } from '@angular/core';
import { BookService } from '../../shared/book.service';
@Component({
selector: 'app-list-books',
templateUrl: './list-books.component.html',
styleUrls: ['./list-books.component.scss']
})
export class ListBooksComponent implements OnInit {
books: any = [];
constructor(private bookService: BookService) {
this.bookService.getBooks().subscribe((item) => {
this.books = item;
});
}
ngOnInit() { }
removeBook(employee, i) {
if (window.confirm('Are you sure?')) {
this.bookService.deleteBook(employee._id)
.subscribe((res) => {
this.books.splice(i, 1);
}
)
}
}
}
In this article we learnt how to build a MEAN Stack CRUD application while implementing the server side rendering concept using Angular universal. We also created an API for our book model. I hope all of this will help you build your dream application without thinking about the SEO because you will use the server side rendering mechanism which is amazing with SEO.
Thank you so much for reading this article.