Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
In SMU Portal, there are many features that use the mailing service. For instance, when you create an account you will receive an email containing a link that to confirm your email address, or when making a box reservation you will receive an email containing the reservation's details.
The mailing service is implemented with nodemailer
coupled with Gmail. In fact, Google offers a free SMTP service to send emails through Gmail.
OAuth 2.0 is a standard that allows your application to use a set of tokens (strings) instead of using actual login credentials (i.e. email and password). This represents an import security advantage since each set of tokens is only bound or valid for a specific action (or set of actions). In this case, our set will only allow sending emails, so once compromised, the tokens cannot do as much harm as actual account credentials.
Before starting, you will need a Gmail account, we recommend that you create a new one instead of using your personal email for extra security measure, you can signup here.
First, we need to create a new project in the Google Cloud Platform. Make sure that you are connected using your newly created account.
Click on Select Project.
This window will pop out. Select New Project and you will be redirected project creation page.
Specify a name and then click Create.
If everything went well, you will be redirected to the dashboard and receive a notification once your project has been created.
Select your project on the top left, click on the burger menu icon, and choose Home > Dashboard.
Click on Go to Project Settings at the bottom of the Project Info card.
Click on the sidebar menu, and choose API & Service > Credentials and you will be redirected to the Credentials page.
Press Create Credentials and select OAuth client ID.
You will be confronted with this page, click on Configure Consent Screen.
Choose External as a User Type and click on Create.
Choose a name for your application and press save.
Once that is finished, click on Credentials from the sidebar, then on Create Credentials and select OAuth client ID as we have done earlier.
Choose Web Application as an Application Type, specify a Name and add https://developers.google.com/oauthplayground to the Authorised redirect URIs list.
Now, your OAuth client has been successfully created, save Client ID and Client Secret, we will need them later on.
Visit OAuth 2.0 Playground, click on the gear icon, and paste your Client ID and Client Secret, also make sure that you have identical options as shown in the image, then press close.
Paste https://mail.google.com/ in the scope text box of Step 1 Select & authorize APIs and click on Authorize APIs
You will be redirected to this page, click on Advance and Go to <PROJECT-NAME> (in this case <PROJECT-NAME> is CS321).
Click on Allow
Press Exchange authorization code for tokens, and it will generate a Refresh token and an Access Token.
Finally, go to your .env
file and add your Client ID, Client Secret, Refresh, and Access Tokens. SMTP_USER
should be your Gmail's email.
If you have successfully completed the previous sections, your .env
file should look like this:
Do not forget to replace all placeholders (e.g. <CLIENT-ID>) with their correct values.
We are all set to start the backend code.
First, install the dependencies using npm i
command.
Once that is finished, you can start the server using npm start
and you should get the following output
If for some reason you got a different output, please make sure that you have correctly followed the instructions from previous sections or you can open an issue.
That's it! The backend server is now running. If you are comfortable enough with backend technologies you can start developing, or you can browse the following sections where we go through a small example of a backend service.
Happy coding 🎉!
In this section, we will fill our database with the necessary data for the application to function properly
Database seeding is the initial seeding of a database with data. Seeding a database is a process in which an initial set of data is provided to a database when it is being installed. It is especially useful when we want to populate the database with data we want to develop in future. This is often an automated process that is executed upon the initial setup of an application. The data can be dummy data or necessary data such as an initial administrator account.
Database seeding. (2020, July 10). Retrieved from https://en.wikipedia.org/wiki/Database_seeding
To seed the database, we will take advantage of a file called seed.js located at models/seed.js
. Running this file creates all the roles and applications that are already implemented in SMU Portal.
After executing npm run seed
we should get the following output:
In this quick tutorial, we will learn how to create a small application (Book Management System) from backend to frontend. We will go through all the steps required for you to make a new module that respects the preexisting architecture of the project.
The purpose of this document is to provide you with the necessary knowledge to start working on the project, both the backend and the frontend part. First, we will go through a brief example that will make use of the frameworks and libraries involved in setting up and running the backend server. Then, we will configure the frontend and introduce the technologies used.
We will begin by defining our database models, then we will create the appropriate services that contain the business logic of our application. Following that, we will expose API routes to forward the supported requests (and any information encoded in request URLs) to the proper controller functions. Finally, we will test our new API routes using Postman.
Creating routes is a fairly straightforward process, we start by adding a new class book.js
in the /routes/api
folder.
Then, we need to import the book service, book model and router.
We should also add the following line:
This allows us to inherit the parameters defined in req.params
at the parent router.
Now, all we have to do is define our API endpoints. Let's create a route that will get us all the books:
Since we need to retrieve the data about all the books registered in the database, we create an HTTP request with a GET method. The URL /getBooks
is our API path.
Caution: /getBooks
is not the complete API path. In this case the complete API path is http://localhost:3000/api/user/getBooks
In fact, our newly created API path is appended to a previously defined path at the parent router. This router is located in the root directory inapp.js.
Next, we add two more routes, one for adding a book and another one to delete a book:
Here, our HTTP request method is a POST method since we will be sending data rather than retrieving it. We extract the title, author and ISBN from the request body. Then, we create an instance of a book with those attributes and we call the addBook()
function that we defined in our book service. If the request succeeds, we will respond with the message "Book added" otherwise "Book not added!" as well as attach a success indicator (a boolean variable in our case).
In this function, we changed the method of request is set to DELETE as we will be removing an entry from the database. This time, we extract the ISBN from the path rather than the body.
As always, we finish by exporting our router.
This part of the guide is a continuation of the previous one where we have created the backend for the Book Management System. In this section, we will provide a step by step guide on how to implement the frontend side of the BMS.
When you are done with this tutorial, your BMS should look like this:
First, install the dependencies using npm i
command.
Once that is finished, you can start the frontend using ng serve
and you should get the following output
If for some reason you got a different output, you can open an .
Happy coding 🎉!
Environment variables allow us to define some configuration that is decoupled from the code-base. It makes the application deployment easier in different environments (e.g. Heroku). Information that varies (hence the term variables) depending on the environment should be included in this file. Also, sensitive information like API tokens or database URLs should not be in this source code or known by unauthorized entities.
Node.js provides a global variable called process.env
which is an that contains all environment variables accessible to the developer. These environment variables are stored in a file called .env
and, by convention, they are defined in a capitalized snake case, e.g DB_URL
.
If you check the repository, you will find a file called, .env.example
that contains variables used by the backend application.
First, fork the repository and clone it on your machine (if you face some issues during this step, you can refer back to the GitHub Guide).
To allow the backend to connect to your database, create a .env
file that is an exact copy of .env.example
and modify it as follows:
Note that changes in the .env
are ignored by git and that is why we provide the .env.example
file to serve as a skeleton or a specification for the variables. As a matter of fact the .env
file should not be pushed.
Just like we did for the creation of the book list component, we will have to navigate to the directory we want our component to be created in.
Then we generate our component bms-add-book
:
We also need to define the path of adding a book to our module.
The code to add a book is rather simple. The user is presented with a form that needs to be filled out. This form contains information about the book (title, author and ISBN) as well as a submit
button. Each filed has a corresponding ID. For instance the input field for the title of the book is identifying by bookTitle
. We will see later why we need these IDs. We also want to add some styling to improve the looks of our form.
To be able to reference a bookForm
, we first have to build a form and specify the fields that we want our form to hold.
When a form is submitted, the function onSubmit()
is called and it takes as an argument the value of the form bookForm.value
.
As a matter of fact, when a book is submitted, all we need to do is to call the service responsible for adding a book with the information that was just submitted.
In any good navigation design, the user should be able to intuitively navigate through the website. To achieve that, we have to offer the user a way through which they can access the add book component as well as exit it or go back to a previous component (book listing component).
Let us go back to the book list component that we have previously created and add a button that redirects the user to the route that we defined for our add book component.
Now, we can reach the add book component from the book listing component by simply clicking on the button "Add Book".
We also want to allow the user to go back to the book listing in case they miss-clicked or simply changed their mind.
Before we start coding, let's break down the project structure of the frontend. At first glance, the number of folders might seem intimidating. However, once we explain the purpose and content of each one of them, you will be able to see the logic behind this particular way of organizing the code.
Our code mainly resides in the src/
folder.
src/
contains three sub-folders namely: app/
, assets/
, environments/
. The following is a table that explains the purpose of each folder.
As you have already noticed by looking at the frontend code-base, src/
is not the only folder at the root of our project. There are other folders such as dist/
and e2e/
. These come much later in the development process and are not in the scope of this tutorial. We do, however, encourage you to read more about Angular's file structure in the link above.
The aforementioned file structure is not a personal architectural decision but rather the default angular skeleton application. On top of that, we have introduced a folder called shared/
. This folder represents a module that contains sub-modules.
In our implementation, the shared module contains components (breadcrumb, home-message and interceptors and user-actions), services, models and pipes. For instance, the breadcrumb component might be used in the dashboard component as well as the profile component. In other words, we want to show the breadcrumb inside the profile page and the dashboard. Since the breadcrumb is being requested by more than one component, it should be part of the shared module.
Now that we have created our API endpoints, we need to check whether they work properly or not. To do so, we will use .
Postman is a collaboration platform for API development. Postman's features simplify each step of building an API and streamline collaboration so you can create better APIs—faster.
The Collaboration Platform for API Development. (n.d.). Retrieved from
Caution: Before proceeding, make sure your server is running. If not, runnpm start
Let's try to test our get call where we ask for a list of the books:
We select GET as request method, and we enter the URL that we previously specified in our router. As a result of sending the request (by pressing the blue button Send), the server returns an empty array since our database has no books yet.
The next step is therefore to create a new book:
Since adding books is done through a POST method, we select that in Postman. Then, we specify the URL and we also provide the necessary data in the body. For that, we also have to make sure that we click on Body
, then raw
and finally select JSON
as format in the drop-down list whose text is highlighted in orange.
If we try to get the list of books again, the book should be displayed.
Finally, we can remove the book that we added. Note that we should add in the URL the ISBN of the book and select DELETE as the request method.
In this part we will create a service that contains our business logic. This service will be called book.service.js
and should be located in the /services
folder.
As we will be manipulated data in our service, we need to import our book model that we have defined earlier.
We will then create a function bookService()
that will contain all the methods that we want to implement.
In this tutorial, we will define three methods: getBooks()
which will retrieve all the books in the database, addBook()
which will be responsible for creating a new book in the database and deleteBook()
which will delete a book from the database.
Finally, we only need to export our service. Exporting our service will allow us to use it elsewhere in the program (just like how we used the book model in the book service module).
The first step is to identify the actors in our application. In this case, we can consider a user and a book as our models. Great! We can now create our classes in the models folder (We will not create a user class since it is already created).
We will use mongoose as our ORM.
Mongoose provides a straight-forward, schema-based solution to model your application data. It includes built-in type casting, validation, query building, business logic hooks and more, out of the box.
Mongoose. (n.d.). Retrieved from
In our case, the book has three attributes, where we define for each attribute its type and other validations.
Note that each book has a unique ISBN and an ISBN is a number that has either 10 or 13 digits.
Source: Workspace and project file structure. (2020). Angular.
SharedModule
is a conventional name for an with the components, directives, and pipes that you use everywhere in your app. This module should consist entirely of declarations
, most of them exported.
NgModule FAQ. (2020). Angular.
After we are done testing the backend, we can move to the frontend part where we will consume our APIs, manipulate the data and display results. In Angular, the principle of separation of concerns is prevalent. We can distinguish three main concepts: modules, components and services. We will go through each one of them as we continue building our Book Management System.
In order to display the app on the dashboard's main page, we need first to add it to the database. We have provided you with a script that allows you to perform this process in an easy fashion.
First step is to pull the script from the original repository (since most likely it is not available on your fork).
Open git bash or your preferred terminal, navigate to your backend repository and type the following commands:
Finally, run npm run createApp
and fill in the prompt with information related to your app, in this, we have done the following,
Caution: Do not forget to revert back to your master branch after creating your app.
For this part you need to navigate to the frontend directory.
To display the app on the dashboard's main page, we need add an entry to the config.json
file. You can find the file at src/assets/apps/
This is how theconfig.json
file should be.
Finally, our app is displayed in the dashboard and the icon is clickable.
APP SUPPORT FILES | PURPOSE |
|
| Contains image and other asset files to be copied as-is when you build your application. |
| Contains build configuration options for particular target environments. By default there is an unnamed standard development environment and a production ("prod") environment. You can define additional target environment configurations. |
In this part we will define some terms and concepts that will repeatedly mention in this section. It is crucial to know what you are doing (or at least have a general idea) instead of blindly writing code (even if it works!).
A component is the main building block of an Angular application, it is composed of: an HTML template that describes what to render on the page, a TS (TypeScript) class that defines the behavior of the component and a CSS selector that defines how the component is used within a template.
To put things into perspective, here is the main page of the Book Management System:
It is divided into multiple components, where each component has its own responsibility (and it can be divided even more).
We have a navbar component, that sits above another component called BmsMainPageComponent
which itself is also containing another component, ListBooksComponent
This feature allows us to reuse parts of code systematically all over the project, like the navbar for instance, without the need of rewriting it each time.
A module is a mechanism that allows to group components, services (which we will define later on in this part), pipes, etc. It is a container for a cohesive block of code that is dedicated to a specific domain. Remember that a good design should be loosely coupled and highly cohesive! In this case, the module is the whole Book Management System.
We can export some elements from the module so that they can be accessed by other modules or keep them hidden so that they are only used internally.
Angular services are singleton object that are instantiated only once during the life cycle of an Angular application. They are used to to share business logic, data and functions across components and modules of the application.
We need to generate a component for our application. In our console, navigate to the book management folder that we have created earlier. Your path should look similar to:
Once reached, we are ready to create our component. Angular allows us to generate a component with a simple command:
We only need to define our component in the module we created. Your book management system module should become the following:
Finally, we create a new file "index.ts" in the list-books folder and add the following line:
Contains the component files in which your application logic and data are defined. See details .
Now these are broad definitions, as we did not want to get into detailed explanation, otherwise this guide would become fairly long and cumbersome to understand. If you are interested in learning more about these concepts, feel free to check the and the section.
Angular Components Overview : Introduction to modules : What Is a Service in Angular and Why Should You Use it? :
The first step is to check our directory path. Once we made sure that we are in the smuportal-frontend, we navigate to applications folder. You can navigate through folders in the console using thecd <directory>
command.
Once you reached the path, run the command:
This will create a new folder that contains a file "book-management-system.module.ts"
We also need to modify our "applications.module.ts" which in the parent folder. We will only modify the routes. We need to define that in our applications module we have added a new application (which is the book management system), and we give the path we want and its module.
Finally, we need to make some small modifications in the box booking since we modified our applications module. We need to modify the file "logistics-reservation.module.ts" by replacing line 14 with:
In addition we need to change the file "box-list.component.ts" by replacing line 96 with:
As adding a book consists of both making a backend call to add an entry to the database and updating the view, we will have to modify our BMS service and module. We will also create a component called bms-add-book
and update previously defined components for navigability.
In this step of this tutorial, we will create a service that will consume the API route that we have defined in the backend such that we retrieve the list of the books. We will then modify our list-book component so that the list of books is displayed.
This process is somehow a little bit similar to the backend part, we start first by creating a book model. In the "src/app/shared/models" we create a new file "book.model.ts" and we add the following code:
Also we add the following line in "index.ts" that is in "src/app/shared/models".
Now, through the console we navigate to the following path:
and we type in the console:
We now need to consume the API route that we have created in the backend. To do so, we modify the "bms.service.ts" file to the following:
We need to check to add in our index.ts that is in "src/app/shared/services".
First we have to add two new functions, namely: addBook
and addNewBook
.
addBook
is responsible for making the backend API call to add a new book. In the request body, we pass the book object that has been specified in the parameters.
addNewBook
is a private method as only the service should be able to update the list of books and no other component. It is responsible for keeping the list of books up to date.
Caution: you should only add the functions addBook and addNewBook rather than replace the entire content of bms.service.ts with the code above.
For the sake of brevity, we omit all unnecessary pieces of code.
Whenever a portion of code is omitted, it is replaced by the following indicator:// ...
If the request does not go through for whatever reason, we will get an error and in that case we simply print it to the console.
Caution: Keep in mind that this is done for demonstration purposes. In reality, all errors should be handled properly and the user should be made aware that the system is misbehaving.
On the other hand, if the request succeeds, we can safely update our list of books that we defined in the service by calling addNewBook
.
As mentioned earlier, addBook
makes a POST HTTP request to the backend server. It sends in the request body the book that it received as an argument. Since HTTP calls are asynchronous, we have to wait for the response of the backend by .
In this section, we will add the last feature of the BMS, which is book deletion. As we have done in earlier parts, we will need to modify the module's service, templates and components.
After assembling all the different parts together, this is how your final code should look like:
We start by defining two functions in the BMS service, deleteBook
and deleteBookByID
:
deleteBook
is responsible for communicating with the API endpoint. We pass to it the book's ISBN as a parameter and the latter will be used as the endpoint's parameter to delete the book from the database.
deleteBookByID
will delete the book from the local list that we have created so that we avoid fetching the list again and provide a better UX.
In deleteBook
we are using the HTTP Client to make a DELETE
HTTP request to the backend server through the API endpoint. Note that we are not specifying the response's type (unlike what we did when fetching the list of books), that is why the function's call type parameter is set to any
.
Once the request has been successfully made, we log the response to the console at line 14 (though this is an optional step, we are just doing so as a sanity check), then we invoke deleteBookByID
to delete the book from the local list.
Caution: Again, in an ideal setting you should properly handle errors by displaying them to your user so they would be aware if the system is not acting properly.
Coming to the deleteBookByID
function, we first create an auxiliary variable of type Book[]
where we store the subject's current value and we operate on that variable. We iterate over the array to find the book with that specific ID. Once found, we delete it from the array using the method splice
, that takes the starting index and the number of elements to be deleted (in the case it is just 1). Finally, we broadcast our new list to the subject.
In this part, we create a new component which will be the main page for our app. We start by checking in our console that we are in the correct directory:
We generate our component by typing in the console:
Now we need to make sure that our book management system module is updated:
Finally, in our book management folder, we create a new file "index.ts" and add the following lines:
Now that our service is ready, we just need to modify our component to display the book list.
Let's start first by importing the service that we have created into our book management system module. Your new module should be similar to the following code:
Our service is imported which means that we are can use it. Let's apply this in our file "list-book.component.ts"
Let's now modify our "list-book.component.html" so that our data will be displayed in a table.
One final step is to modify our main page component. We want that our main component contains the list book component. We modify the "bms-main-page.component.html"
And we also add some styling by modifying the "bms-main-page.component.css"
The first step is to create a MongoDB database. Since this project is about collaborative development, we will be hosting the database on the cloud rather than locally
First, visit MongoDB's website to create a Cluster that will be hosted on the cloud.
Click on Start free and you will be redirected to the registration form.
Once that is finished, you will be asked to enter your organization's and project's names (in this case I kept the default proposed names) and the preferred language (though this step is not required).
Once you finished signing up, you will be redirected to the cluster creation page. Choose Shared Clusters as your plan (unless you feel fancy then you can choose a paid plan).
In this step, you will choose a cloud provider (AWS, Google Cloud, or Azure) and the region depending on the provider, then click on the Create Cluster green button at the bottom right.
If everything went well, you will be redirected to your dashboard, and after a few minutes (~1-3min) your cluster should be ready.
The last step is to retrieve the connection string (i.e. database URI) that you will use to connect the backend to the database. Click on the Connect button under the cluster's name.
Click on Allow Access from Anywhere, this will allow anyone to connect to your database given the string. The 0.0.0.0/0 IP address will be added to the whitelist. Don't forget to click on Add Ip Address to save.
Next, you will need to create a user that has permission to connect to the database. For security reasons, it is always recommended to use the auto-generated password. Once you are done, click on Create Database User
Then, click on Choose a connection method, and select Connect your application
Select Node.js as the driver and 3.6 or later as the version.
In the grey box, a string under the following format will be located.
Where <username> is the database user's username (in this case it's mootez), <password> is the password for the newly created user, and <dbname> is the database name that we will create. You can copy the string and save it elsewhere and click on the Close button.
For the final part, we will create a database in our cluster.
Click on your cluster's name and navigate to the Collections tab. Click on Add my Own Data and specify a name and a collection for your database (collections are analogous to tables in relational databases, so a database can have multiple collections), in this case, I chose smu as a DB name and users as a first collection.
Don't forget to replace <dbname> in the connection string with the database's name that you have chosen (it is smu in this case).
You have now successfully created a MongoDB database. In the following section, we will show how to connect the backend code to communicate with the database.
In this part, we have illustrated how to implement the frontend side of the Book Management System module.
Though not in detailed way, but we have covered multiple concepts such as Component Navigation, Forms Handling, Event Binding, API Communication via HTTP Client and so on. These concepts should be enough to help you implement the frontend part of your module. If you have encountered any problems, you can refer the Book Management System repositories (and ) where you can find the full code base and please make sure that you have followed the steps thoroughly.
Other than that, good luck and happy coding 🎉!
We will now update the ListBooksComponent
, so that we allow the user to actually delete books from the list. What we will do first is to add some changes to the template (i.e. the HTML file) of the component and then we will modify the component's TypeScript class.
Basically, what we want to achieve is to display a red button labeled "Delete" , when clicked, will remove the row from the table and display a message confirming that indeed the book has been removed.
Go to list-books.component.html
and modify it as follow,
What we have done is that we have simply created a button inside the for
loop (so that it is created at each row) and we have bound a function deleteBook()
that takes the book's ISBN as a parameter to the click
event (if you want to read more about in Angular). What will happen, is that if the user clicks on the button, that function will be triggered.
What remains now is to add the confirmation message once the user has deleted the book. Go to the top of the file add the following code block,
If you look closely, you will notice that the message has an if
directive, and this makes perfect sens since we do not want the message to be always displayed, rather, it should be visible only after deleting, so its status would depend on the value of bookRemoved
(which is a boolean variable).
The last step is to define the functions and variables (deleteBook()
and bookRemoved
) in the component's logic.
What the deleteBook()
is actually doing, is calling the previous method that we have implemented in the Book Management System's Service that is responsible for deleting books from the list and database , and then we are changing the state of bookRemoved
to true
so that the confirmation message is displayed.
By now, ListBooksComponent
files and BMS service should be look like this,
In this section, we will display errors that students have reported followed by their fixes. (Last updated on November, 10th)
Error: self signed certificate in certificate chain Source: backend, nodemailer
Solution: Something is blocking the mailing server from establishing a TLS connection with Google's SMTP server. Disabling the antivirus can help prevent such error.
Error: An unhandled exception occurred: Job name "..getProjectMetadata" does not exist. Error: An unhandled exception occurred: Cannot read property 'Minus' of undefined.
Solution: Your global angular installation and your local one are conflicting. Make sure that you have the same angular version installed (globally and locally) or remove the global angular installation by running: