The MEAN stack allows you to build complete applications using one programming language: JavaScript. In this tutorial, we made upon the first part (Creating an Angular app) which built the front-end, and this part builds the backend with a RESTful API and Database.
REST API with Node.js
We are going to use express-generator and create a folder called server
.
First, install the generator packages:
1 | npm i -g express-generator |
Note: You should have Node and NPM/Yarn installed.
REST API using ExpressJS
Now let’s scaffold the App using the generator:
1 | express server -e |
Let’s install all its dependencies on the server folder:
1 | cd server && npm i |
and now let’s make sure it’s working:
1 | npm start |
Go to localhost on port 3000 and make sure you can see a “Welcome to Express.”
Changes: a3fcacd - REST API using ExpressJS: scaffold
Creating a host alias for the server
We want to run the server to work regardless of the environment where we run it. (It will be useful for Docker later on)
For that we can create an alias by editing the hosts
:
- Windows:
c:\windows\system32\drivers\etc\hosts
- Linux/Mac:
/etc/hosts
Once you can open the file, you drop the following line at the end:
1 | 127.0.0.1 server |
Now you should be able to access the server by visiting:
(If you have trouble editing the host file, take a look here)
Creating API routes and responding to requests
Now we are going to create a new route:
1 | /api/todos[/:id] |
This route will get all our todos, update, and delete them.
Create a new router file called todos in the following path:
1 | touch server/routes/todos.js |
and add this initial content:
1 | const express = require('express'); |
All this is doing is replying to the GET commands and returning a hard-coded list of todos. We will replace it later to get data from mongo instead.
Then we need to register the route as follows:
1 | var todosRouter = require('./routes/todos'); |
The method use
registers the new path /api/todos
. When we get any call on this path, our todosRouter
will handle it.
You can restart your server or use nodemon to pick up changes and refresh the browser.
1 | ## npm i -g nodemon |
That should get your server running. Now you can see it in action using cURL:
1 | curl -XGET server:3000/api/todos |
This command should get you all the lists in JSON format!
In the next step, we will query the server using Angular instead of curl
.
After that, we will complete the rest of the operations (update, delete, create).
6f8a502 - Creating API routes and responding to requests
Connecting REST API with Angular App.
Let’s now prepare our angular App to use the server API that we just created.
As you might know, when you run ng serve
, it will trigger a development server.
However, our API is an entirely different server. To be able to connect the two, we need to create a proxy.
Creating a proxy in Angular to talk to the API server
Let’s create a new file that will tell Angular when to look for specific HTTP paths.
In this case, we are going to defer all /api
to our express server.
1 | { |
(This will need the host alias from the step before)
Then, we have to tell Angular to load this file when we are serving the App.
We are going to do that in the angular.json
file.
If you are using the same version of angular CLI, you need to insert this on line 71:
1 | "proxyConfig": "src/proxy.conf.json" |
For some context, here are the surrounding elements:
1 | { |
Now our App will pass all requests that start with /api
to http://localhost:3000
(or whatever path you specified on the proxy.conf).
Next, we are going to make use of these new routes!
e81ddb8 - Creating a proxy in Angular to talk to the API server
Using HTTP Client to talk to the server
To talk to the server, we are going to use the HttpClient
module.
Let’s go to the app.module and let’s import it:
1 | import { HttpClientModule } from '@angular/common/http'; |
Now that the HttpClient is available in our App let’s add it to the service and make use of it.
1 | import { HttpClient } from '@angular/common/http'; |
We change the TodoService.get
to use HTTP client. However, the component was responding to a Promise, and the HTTP.get returns an Observable. So, let’s change it.
Change the getTodos method from the old one to use this one that handles an observable.
1 | getTodos(query = '') { |
The main difference is that instead of a .then
, we are using .subscribe
. Everything else remains the same (for now).
That’s it, let’s test it out!
Run these commands on your terminal:
1 | ## run node server |
on another terminal session run also:
1 | ## run angular App |
Once you have both running, you can go to http://localhost:4200/all, and you can verify that it’s coming from your server!
If you are running nodemon
, you can change the TODOS on server/routes/todos.js
and refresh the browser, and see how it changes.
But, we don’t want to have hard-coded tasks. Let’s create a proper DB with Mongo.
Setting up MongoDB
It’s time to get MongoDB up and running. If don’t have it installed, you have a couple of options:
Docker (Windows/macOS/Linux) [Preferred]
Download the docker engine
Pull Mongo image
1
docker pull mongo
NOTE: More details in the rest of the post.
Official Website (Windows/macOS/Linux)
You can download it from here:
https://docs.mongodb.com/manual/administration/install-community/
Brew (macOS)
1 | brew tap mongodb/brew |
We are going to use Docker since it’s an excellent way to have everything running together with one command. Also, you can deploy it to the cloud and scale it quickly.
Dockerizing the MEAN stack
Let’s get everything running (Node Server, Angular, and Mongo). We will create a docker-compose file, which is going to list all our services, and we can run them all at once.
1 | version: "3.7" |
All right, now we can get the whole full-stack App running with one command:
1 | docker-compose up --build |
NOTE: close other terminals running a web server so the ports don’t conflict. The docker-compose command will create 3 containers for our Angular App, Node Server, and Mongo DB. You can also see all the logs in one place.
After you wait a minute or so, you should be able to open the App on http://localhost:4200/.
Now we can make use of mongo. Keep docker-compose running and now let’s remove the hard-coded tests and use the database.
0763db0 - docker compose
Creating MongoDB schema with Mongoose
Let’s install Mongoose, which is a library for managing MongoDB from Node.js.
1 | cd server && npm i mongoose@5.9.18 |
NOTE: make sure you installed it on the ./server/package.json
, rather than the client-side packages ./packages.json
.
The first thing we need to do is to connect to Mongo when our server starts. Go to server/app.js
and add the following code:
1 | const mongoose = require('mongoose'); |
15f6e25 - add db string to connect to mongo
We can pass some ENV variables like MONGO_HOST
. If we run it locally, it will use localhost, but if you run it on Docker, we want to pass a hostname. You can see that in the docker-compose.yml
file.
Now, let’s define our data model for Mongo. Let’s create a folder models
inside server
and add a file called “todos.js”.
1 | const mongoose = require('mongoose'); |
The new schema is defining what fields we want to store and what the types are.
The updated_at
will update automatically when we create a new todo.
436b0ad - npm i mongoose
b2674f3 - Creating MongoDB schema with Mongoose
Adding all the API routes to modify data in the DB
Let’s add all the routes to create, read, update, and delete data from Mongo.
The Mongoose library provides some convenient methods to do CRUD operations:
- ** Todo.find**: find data matching a given query. (
{}
, get all, while{isDone: true}
get only completed tasks). - ** Todo.create**: Create a new todo
- ** Todo.findByIdAndUpdate**: Find Todo by given id and update its content.
- ** Todo.findByIdAndDelete**: Find Todo by given id and delete it.
- ** Todo.deleteMany**: Delete everything matching a given query.
Here are the routes by their matching HTTP verb (GET, PUT, POST, DELETE). In the next sections, we will test all these routes and go over some more details.
1 | const express = require('express'); |
We added many routes to this step. Take your time to go through them.
In the next section, we will test them using curl
and then integrated them with Angular.
f4f2281 - Adding all all the API routes to modify data in DB
Testing the API CRUD operations
Since we installed a new package, mongoose
, we have to run npm install
in the docker containers. Otherwise, file changes are picked up automatically, and you don’t need to restart.
Stop docker-compose
and start it again docker-compose up --build
.
Creating a new task and getting lists
You can create a new task using the following command:
1 | curl -XPOST server:3000/api/todos -H "Content-Type: application/json" -d '{"title": "CRUD API", "isDone": false}' |
Now, let’s see if it’s there:
1 | curl -XGET server:3000/api/todos |
You should have got something like this:
1 | [{"_id":"5edc2a6d0c41d60054ad715f","title":"CRUD API","isDone":false,"updated_at":"2020-06-06T23:44:45.966Z","__v":0}]⏎ |
You can also check Angular on http://localhost:4200/all. The new task should be there!
Update data with PUT method
If you remember from your routes file, we are using the method PUT to update tasks.
1 | /* PUT /api/todos */ |
By default findByIdAndUpdate
returns the original document.
We are passing { new: true }
so we can return the updated document.
For updating a task you need the _id
. You can get it from the previous step, when we listed all the tasks. For my case the _id is 5edc2a6d0c41d60054ad715f
, find yours and replace it in the next command:
1 | curl -XPUT server:3000/api/todos/5edc2a6d0c41d60054ad715f -H "Content-Type: application/json" -d '{"title": "Finish PUT API", "isDone": true, "note": "New Field"}' |
As you can see in the last update, we can modify existing fields and add new values like the note
field.
Erasing data with the DELETE method
For our todo route, we also defined the DELETE method. Similar to the update, we need to pass and id
.
1 | /* DELETE /api/todos */ |
Again, remember to replace the next call with yout _id
:
1 | curl -X DELETE server:3000/api/todos/5edc2a6d0c41d60054ad715f |
If you check the UI, all tasks will be gone: http://localhost:4200/all.
As much fun as curl
is, let’s move on and complete all these functionalities in Angular.
Angular Service to talk to the server
There are two main changes that we need to make in other to use the API server.
- We need to change the
TodoService
service to use HTTP client. - Change the
TodoComponent
component to use the methods.
Angular service using the HTTP client
In the following code, we are using the HTTP client, to make the appropriate calls:
1 | import { Injectable } from '@angular/core'; |
The Todo service matches the HTTP verbs that we used in curl and passes the payloads.
Let’s now change the TodoComponent that goes along with these changes.
a93291c - Angular service using HTTP client
Angular TodoComponet updates
Here’s what your component should look like this:
1 | import { Component, OnInit } from '@angular/core'; |
Let’s go over each part in the next sections.
Sending Queries with HTTP GET
In the component, one the first thing we do is check the route params (path):
1 | ngOnInit() { |
When you click on the buttons All
, Active
, and Completed
, that will trigger a route change.
To recap, these buttons use the router link. So, every time you click on them, they will change the URL.
1 | <ul class="filters"> |
After we change the URL, the next thing we do is to call getTodos
. Let’s see that next.
Get all todos
We can get all services using the following:
1 | getTodos(route = 'all') { |
We this.todoService.get
will issue an HTTP get and retrieve all the tasks from the database and update the todos. It also updates the number of active tasks (the ones that are not done).
The getTodos
receives an argument (route
) with the path that will be one of these: all
, active
, or complete
. However, MongoDB doesn’t understand these words. We have to map it (mapToQuery
) to something a proper query like { isDone: true }
. This MongoDB will understand.
e33d540 - Angular TodoComponet updates
Modifying the todos
All the other operations, like the update, clear, toggle, are very similar. They trigger an action and then call getTodos
, so the UI is up to date with the latest changes.
That’s all!