Angular 6 — A simple web application with Node and PostgreSQL
I decided that I wanted to learn Angular 6. I work in the software industry, so I like to keep up to date on trends but don’t get much of a chance to put things into practice.
Through my learning I leveraged many great tutorials, but I didn’t find one that ran through the whole creation process using the particular technologies that I choose- from database to front-end. So I decided to document my process for creating a simple App and hope that it is useful for other beginners. It’s by no means perfect, so comments and suggestions are welcome.
The aim of my app is to display a list of countries and currency exchange rates. The data is stored in a database on my server. Granted, it’s probably not the most practical example as currency rates are generally live and aren’t statically stored in a database, but hopefully the tutorial is useful for someone using other data.
The main technologies that I will use:
- Angular (version 6) CLI — Typescript components for front-end/client development
- Node & Express — Server and Router development written with Typescript
- PostgreSQL Database (version 10.3) will be my data-store
- Sequelize-Typescript as my ORM for communication between the Database and my server
Some details on the environment (although it should be fairly agnostic to environment):
- Operating system: Ubuntu 18.04
- IDE: VS Code
- Github as my repository
- Node & NPM as my package manager
I will try to work through the process in a logical way…but in reality it was far from logical in practice because I was also going through a learning process. Here is a general outline which I will explain in this tutorial and, in hindsight, I think it is the best way to approach the tasks.
- Set-up the project with a basic skeleton for the WebApp using Angular CLI
- Create a Node Express server. This will be used to connect to the database and act as an API server delivering data to the client side
- Generate a data model with Sequelize-typescript. The model and data can be created in code and stored in the actual database in PostgreSQL
- Connect the Database to the server using Sequelize-typescript ORM
- Create the routing for the API to allow access to the data
- Switch to the client side of the webApp and create a front-end component to display the data
- Create a service for that component that observes the API and pulls the data into the component for display
You can download my project from my Github.
Project Creation
I used Angular CLI to generate the project and related files. Details can be found on the Angular site. This created the basic skeleton.
My starting folder structure looked like:
|_[ .vscode ] |_[ e2e ] |_[ src ] |_[ app ] |_[ assets ] |_[ environments] |--browserslist |--favicon.ico |--index.html |--karma.conf.js |--main.ts |--polyfills.ts |--styles.css |--test.ts |--tsconfig.app.json |--tsconfig.spec.json |--tslint.json |--.editorconfig |--.gitignore |--README.md |--angular.json |--package.json |--tsconfig.json |--tslint.json
I kept all the defaults in my tsconfig.json so the output Javascript files are located in a separate dist/out-src directory. This essentially means that if I want to run an individual file with node, I need to build using VSCode’s TSC build and then switch to the output directory to find the JS file and from there can run ‘node server.js’ for example.
Front-end building and serving are handled by ‘ng build’ or ‘ng serve’ as documented on the Angular quick-start guide linked above.
Server side
Within SRC folder, I created a server folder. This will hold the server files and the database files.
Set up server app
I create the file server.ts that simply creates my server at site ‘http://localhost:3000/api'.
import * as express from 'express';
const hostname = 'localhost'; const port = 3000; const server = express();
server.get('/api/', (req, res, next) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Hello World'); });
server.listen(port, hostname, () => { // connect to the DB console.log(`Server running at http://${hostname}:${port}/`); });
You’ll notice that I added the ‘api’ word into the server.get line. This is just because I wanted all the addresses of my routes to have API in the address, so it is my preference.
Create and Connect to DB
Next, I wanted to connect to my database. Here are the main steps…
Firstly, I needed to download and install PostgreSQL on my machine. I also installed PgAdmin3 so that I could see what was happening in the Database. Just a note, that I did find some issues with PgAdmin and PostgreSQL v10 so you may wish to use a different tool to play with your DB.
I pretty much followed the instructions given in this tutorial. I created a user called ‘postgres’ with password. I also created my database and called it ‘travelbudget’. This was done using terminal and PgAdmin.
As I mentioned earlier, we are going to use sequelizer-typescript as our ORM to connect and talk to our database. The sequelize-typescript has some great documentation and I found the owner of the project very helpful and responsive to questions.
Now I go back to my VSCode and created two files in the server directory:
config.ts database.ts
The config file contains the parameters of the database we just created and some other information. You don’t need to separate out the config file and most people probably put the details directly into the database.ts file. Here is my config.ts file:
export const dbconfig = { username : "postgres", password : "postgres", database : "travelbudget", host : "localhost", dialect : "postgres", port : 5432 }
My database.ts file looks like this:
import {Sequelize} from 'sequelize-typescript'; import { dbconfig } from '../../config'; // DB connection parameters
export const sequelize = new Sequelize({ database: dbconfig.database, dialect: dbconfig.dialect, username: dbconfig.username, password: dbconfig.password, host: dbconfig.host, port: dbconfig.port }); sequelize.authenticate().then(() => { console.log("Connected to DB"); }) .catch((err) => { console.log(err); })
The sequelize.authenticate line is added in just to test if a successful connection was made. It’s not necessary so you can remove it. To test if the connection is working, simple build the project in VSCode and cd to the ${projectdirectory}/dist/out-src/src/server and run ‘node database.js’.
Generate model and populate some data
Now that we’ve made a successful connection, we can create our first model. Our data model is going to be very simple as we only need the country and the exchange rate. However, we can add some automated columns using sequelize-typescript. I decided to add two additional columns to record the ‘createdAt’ and ‘updatedAt’.
I create a folder called ‘models’ in the server folder. In that folder I created a file called currencymodel.ts which contains:
import {Table, Column, Model, CreatedAt, UpdatedAt} from 'sequelize-typescript';
@Table export class Currency extends Model<Currency> {
@Column country: string;
@Column exchangerate: number;
@CreatedAt @Column createdAt: Date;
@UpdatedAt @Column updatedAt: Date; }
We then need to add the model to our sequelize object that is in our database.ts. So in our database.ts file, I add the lines:
import { Currency } from './models/currencymodel'; ... sequelize.addModels([Currency]);
We can do a few things here, just to play with the sequelize-typescript library. For instance, if we want to create the Currency table in the database, we can do that through code in the database.ts file. So I created a function called:
// Force Initialization of the models and wipe all data /// function initializeDatabase() { sequelize .sync({ force: true }) .then(() => { console.log('Connection synced') return; }) .catch(err => { console.log('err'); }); }
This is only called once as it forces the deletion of any Currency tables on the DB. You can check PGAdmin to see that the Table was created.
You can also populate some data using another function called:
// Adding new currencies to the DB /// function populateData(){ const mycurrency = new Currency({ country: 'Ireland', exchangerate: 100 }); mycurrency.save() .then(() => { console.log("City " + mycurrency.country + " added to DB"); }) .catch((err) => { console.log(err); }) }
This code will add Ireland item to the database. The ‘createdAt’ and ‘updatedAt’ columns are automatically filled when the code is run.
Two things that are specific to sequelize-typescript is that it will pluralize the name of the model (e.g. Currency Model becomes Currencies Table, Person Model becomes People Table). Also, it adds an Id column to the Table. There’s tonnes of other stuff there but let’s leave it for now and move ahead with our simple example.
Create API route
Now that we have created our model and connected to the Database, we need to create an API to allow access to the data. This will be done using Express routing. We need to create a folder in the server folder can call it routes. Within that folder, I create a file called currencyRouter.ts, which looks like this:
import {Router} from 'express'; import {Currency} from '../models/currencymodel'; // From https://github.com/RobinBuschmann/sequelize-typescript-example/blob/master/lib/routes/movies.ts export const currencies = Router();
// Initial get everything route currencies.get('/', (req, res, next) => { Currency .findAll() .then((data) => { return res.json(data); }) .catch((err) => { console.log(err); return err; }) });
currencies.get('/:id', async (req, res, next) => { try { const currency = await Currency.scope(req.query['scope']) .findById(req.params['id']); res.json(currency); } catch (e) { next(e); } });
// post currencies.post('/', async (req, res, next) => { try { console.log(req.body); const currency = await Currency.create(req.body); res.status(201).json(currency); } catch (e) { next(e); } });
// update api/id currencies.put('/:id', async (req, res, next) => { try { await Currency .update<Currency>(req.body, {where: {id: req.params['id']}}); res.sendStatus(200); } catch (e) { next(e); } });
// delete api/id
I just added the simple first route which will return all the data in the Currency table. I left some TODOs in the file where I could go back to fill in the extra get by ID, post new, delete by ID, update by ID. For our simple example, these aren’t required but it would be useful to have a fully functioning API if we were to do some more complex stuff.
Now we need to add this new Route to our server. Back to our server.ts file, I add the following lines of code:
import {currencies} from './routes/currencyRouter'; ... server.use('/api/currencies', currencies);
As we will be accessing the API from our Client server, we have to add an additional piece of code to our server.ts file to Allow access. Otherwise, our server will reject calls from the Client App. So in our server.ts, we add the following lines:
// allow access from client server server.use(function (req, res, next) {
// Website you wish to allow to connect res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200');
// Request methods you wish to allow res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent // to the API (e.g. in case you use sessions) res.setHeader('Access-Control-Allow-Credentials', false);
// Pass to next layer of middleware next(); });
Now when we launch our server again, we can go to http://localhost:3000/api/currencies and view our data in the browser. Very nice!! Now we have completed all we need to do on the server side…let’s switch over to the client side to see if we can view the data.
Client side
Let’s leave the server folder and head on over to the {projectfolder}/src/app folder. Here we have our default app which includes a ts, html, css and spec.ts files. The spec.ts file is just used for testing so we will ignore it for now.
If you want to see what the skeleton client side looks like, you can run ‘ng serve’ either from command line or through the VSCode — Run Task command.
You can take a look into the app.component.html file and make the page look nice. I didn’t spend too much time making it look pretty so I won’t share my file on here.
Create component to hold data
As Angular 6 is component based, I decided to create a component called currency-converter using the command
ng generate component currency-converter
This created a folder inside my app folder called currency-converter which contained my ts, html, css and spec.ts files again.
Create service to listen to API
In order to access the data from our API, we need to create a service that listens to our API. To do this, at the app level, I created the service using
ng generate service api
This created a file called api.service.ts. Here are the contents I added to make it into a HTTP Client:
import { Injectable } from '@angular/core'; import {HttpClient, HttpHeaders } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class ApiService {
baseURL = 'http://localhost:3000/api/'; constructor(private http:HttpClient) { };
getAllData(apiItem: String): any { return this.http.get(this.baseURL+apiItem, {responseType: 'json'}); } }
Essentially, this looks at the base URL and has only one function that which returns all the data. To make it generic, I pass this function the name of the route to get the data. Similar to my TODO tags on the Currency Router file, I could go through and populate with additional functionality.
Displaying the data within the Component
Moving back to my currency-converter component, I need to make some changes to have it use my new API service. So I added the following lines to my currency-converter.component.ts
import { Component, OnInit } from '@angular/core'; import { Currency } from '../../server/models/currencymodel'; import {ApiService} from '../api.service';
...
@Component({ selector: 'app-currency-converter', templateUrl: './currency-converter.component.html', styleUrls: ['./currency-converter.component.css'] }) export class CurrencyConverterComponent implements OnInit { public currencydata: Currency; constructor(private apiService: ApiService) { } ngOnInit() { this.apiService.getAllData('currencies').subscribe( data => this.currencydata = data, err => console.log(err), () => console.log("completed") ); } }
Now, to display on the front end, I edited my currency-converter.component.html
<p> <li *ngFor="let currency of currencydata"> {{$index}} {{currency.country}} : {{currency.exchangerate}} </li> </p>
Final step, we need to add my currency-converter Component to my main app. This is simply done by adding the following line to our app.component.html file:
... <app-currency-converter></app-currency-converter>
Pulling everything together
Run
ng serve
for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
Run
node server.js
from the `./dist/out-src/src/server` folder to launch the API server. The API server is availalbe at `http://localhost:3000/api/`
0 comentarios:
Publicar un comentario