masaj salonu masaj salonları
Home » Advertising » Full Stack Node Development

Full Stack Node Development

Back in August, I had participated in Midwest JS located in Minneapolis, Minnesota. As you may know, I’m a huge fan of developing full stack applications with the JavaScript stack. This is exactly what I had presented on at the conference.

My session was well attended and many developers were taught how to use Node.js with Couchbase to develop a RESTful API and Angular as the client facing layer.

As promised, I am going to revisit the material I went over during the presentation so the concepts and code can be reproduced and expanded upon.

Going forward, the assumption is that you’ve got Couchbase Server, Node.js, and the Ionic Framework CLI installed and configured. Couchbase will be the NoSQL Database, Node.js will power our backend, and Ionic Framework will give us a web frontend powered by Angular.

The project created at Midwest JS allowed you to store information about video game consoles and video games for various consoles. This demonstrated the use of CRUD as well as relationships between NoSQL documents and how Couchbase makes it easy.

Creating the Node.js With Couchbase NoSQL Backend

Before we can begin development, we need to create a new Node.js project. From the command line, execute the following:

npm init -y
npm install couchbase express body-parser uuid cors --save

The above command will create a project package.json file and install our project dependencies. The cors package will allow us to use Node and Angular locally on two different ports without getting cross-origin resource sharing errors. The uuid package will allow us to generate unique strings for use as document keys. The body-parser package will allow us to send JSON data in HTTP requests. We’ll be using Express and Couchbase which explains the other two packages.

Create an app.js file within your project. It will contain all the source code for our Node.js application. As boilerplate, it should look like the following:

var Couchbase = require("couchbase");
var Express = require("express");
var BodyParser = require("body-parser");
var UUID = require("uuid");
var Cors = require("cors");

var app = Express();
var N1qlQuery = Couchbase.N1qlQuery;

app.use(BodyParser.json());
app.use(BodyParser.urlencoded({ extended: true }));
app.use(Cors());

var cluster = new Couchbase.Cluster("couchbase://localhost");
var bucket = cluster.openBucket("default", "");

app.get("/consoles", (request, response) = {});
app.post("/console", (request, response) = {});
app.post("/game", (request, response) = {});
app.get("/games", (request, response) = {});
app.get("/game/:id", (request, response) = {});

var server = app.listen(3000, () = {
    console.log("Listening on port " + server.address().port + "...");
});

Notice that we’ve imported each of the downloaded dependencies, initialized and configured Express, and connected to a Bucket in our Couchbase cluster.

We will have five different RESTful API endpoints for this application.

The first logical thing to do would be to create a video game console so we can add games to it. Take a look at the following endpoint code:

app.post("/console", (request, response) = {
    if(!request.body.title) {
        return response.status(401).send({ "message": "A `title` is required." });
    }
    var id = UUID.v4();
    request.body.type = "console";
    bucket.insert(id, request.body, (error, result) = {
        if(error) {
            return response.status(500).send(error);
        }
        response.send(result);
    });
});

In the above logic, we are validating that a title exists in our request. If it does, we will generate a new id, assign a type to the data in our request, and insert it into Couchbase. The success or failure response of the insert will be returned to the client, which will eventually be an Angular application.

To query for video game consoles, we’ll need to query based on the type property. For this reason, we’ll have to use a N1QL query rather than a lookup by id.

app.get("/consoles", (request, response) = {
    var statement = "SELECT `" + bucket._name + "`.*, META().id FROM `" + bucket._name + "` WHERE type = 'console'";
    var query = N1qlQuery.fromString(statement);
    query.consistency(N1qlQuery.Consistency.REQUEST_PLUS);
    bucket.query(query, (error, result) = {
        if(error) {
            return response.status(500).send(error);
        }
        response.send(result);
    });
});

The N1QL query is nothing more than a simple SELECT statement that you’d find in SQL. After executing the query, we would return the response back to the client.

This brings us to the actual video games. Things get a little more complex, but not more difficult.

app.post("/game", (request, response) = {
    if(!request.body.title) {
        return response.status(401).send({ "message": "A `title` is required." });
    } else if(!request.body.cid) {
        return response.status(401).send({ "message": "A `cid` is required." });
    }
    var id = UUID.v4();
    request.body.type = "game";
    bucket.insert(id, request.body, (error, result) = {
        if(error) {
            return response.status(500).send(error);
        }
        response.send(result);
    });
});

In the above endpoint logic, we plan to insert a new video game into the database. This is no different than what we saw when inserting a new video game console into the database. We are defining a type property, but we are also making sure a cid exists. The cid will be a console id which will allow us to establish a relationship with our data.

When you have relationships, you have JOIN operations.

app.get("/games", (request, response) = {
    var statement = "SELECT game.title AS game_title, console.title AS console_title FROM `" + bucket._name + "` AS game JOIN `" + bucket._name + "` AS console ON KEYS game.cid WHERE game.type = 'game'";
    var query = N1qlQuery.fromString(statement);
    query.consistency(N1qlQuery.Consistency.REQUEST_PLUS);
    bucket.query(query, (error, result) = {
        if(error) {
            return response.status(500).send(error);
        }
        response.send(result);
    });
});

In the above endpoint, we are doing another N1QL query, but this time we have a JOIN operation. It isn’t useful to return a cid when querying for video games, so we JOIN and replace that information with the console title of the other document.

Likewise, we have a similar query when trying to find a specific video game:

app.get("/game/:id", (request, response) = {
    if(!request.params.id) {
        return response.status(401).send({ "message": "An `id` is required." });
    }
    var statement = "SELECT game.title AS game_title, console.title AS console_title FROM `" + bucket._name + "` AS game JOIN `" + bucket._name + "` AS console ON KEYS game.cid WHERE game.type = 'game' AND META(game).id = $id";
    var query = N1qlQuery.fromString(statement);
    bucket.query(query, { "id": request.params.id }, (error, result) = {
        if(error) {
            return response.status(500).send(error);
        }
        response.send(result);
    });
});

The alternative to using N1QL and JOIN operations would be to do two lookups based on id. There is nothing wrong with this practice, but, in my opinion, it is easier to just let the database take care of a JOIN rather than trying to JOIN via the application layer.

This brings us to the client frontend.

Creating the Ionic Framework With Angular Frontend

As previously mentioned, this time around we are using the Ionic Framework which uses a flavor of Angular. I chose this because I was feeling too lazy to create an attractive frontend with Bootstrap or Foundation.

With the Ionic Framework CLI available, execute the following:

ionic start pwa sidemenu

The above command will create a project called pwa using the Ionic Framework sidemenu template.

The base template is useful, but it doesn’t have everything we need. We need to add a few pages to the application.

Using the Ionic Framework generators, or manually, create a consoles, games, and game page. Each of these pages should have an HTML, SCSS, and TypeScript file and each page directory should be in the pages directory.

Open the project’s app/app.component.ts file and make it look like the following:

import { Component, ViewChild } from '@angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

import { GamesPage } from '../pages/games/games';
import { ConsolesPage } from '../pages/consoles/consoles';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  @ViewChild(Nav) nav: Nav;

  rootPage: any = GamesPage;

  pages: Array{title: string, component: any};

  constructor(public platform: Platform, public statusBar: StatusBar, public splashScreen: SplashScreen) {
    this.initializeApp();

    // used for an example of ngFor and navigation
    this.pages = [
      { title: 'Games', component: GamesPage },
      { title: 'Consoles', component: ConsolesPage }
    ];

  }

  initializeApp() {
    this.platform.ready().then(() = {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      this.statusBar.styleDefault();
      this.splashScreen.hide();
    });
  }

  openPage(page) {
    // Reset the content nav to have just this page
    // we wouldn't want the back button to show in this scenario
    this.nav.setRoot(page.component);
  }
}

Notice that we’ve imported GamesPage and ConsolesPage, updated the pages array, and set the default root page as GamesPage. By doing this we’ve set up navigation and the default page when the application launches.

To complete the setup, we also need to alter the project’s app/app.module.ts file. Make it look like the following:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { HttpModule } from "@angular/http";

import { MyApp } from './app.component';
import { GamesPage } from '../pages/games/games';
import { GamePage } from '../pages/game/game';
import { ConsolesPage } from '../pages/consoles/consoles';

import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

@NgModule({
  declarations: [
    MyApp,
    GamesPage,
    GamePage,
    ConsolesPage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp),
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    GamesPage,
    GamePage,
    ConsolesPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

Notice that we’ve imported each of our new pages and added them to the declarations and entryComponents arrays of the @NgModule block.

Now we can focus on the development of the application and connecting it to our API.

Open the project’s src/pages/games/games.ts file and make it look like the following. We’re going to break down what is happening next.

import { Component } from '@angular/core';
import { NavController, ModalController } from 'ionic-angular';
import { Http, Headers, RequestOptions } from "@angular/http";
import "rxjs/Rx";
import { GamePage } from "../game/game";

@Component({
    selector: 'page-games',
    templateUrl: 'games.html'
})
export class GamesPage {

    public games: Arrayany;

    public constructor(public navCtrl: NavController, private http: Http, private modalCtrl: ModalController) {
        this.games = [];
    }

    public ionViewDidEnter() {
        this.http.get("http://localhost:3000/games")
            .map(result = result.json())
            .subscribe(result = {
                this.games = result;
            });
    }

    public create() {
        let gameModal = this.modalCtrl.create(GamePage);
        gameModal.onDidDismiss(data = {
            let headers = new Headers({ "Content-Type": "application/json" });
            let options = new RequestOptions({ headers: headers });
            this.http.post("http://localhost:3000/game", JSON.stringify(data), options)
                .subscribe(result = {
                    this.games.push({ "game_title": data.title, "console_title": ""});
                });
        });
        gameModal.present();
    }

}

Within the GamesPage class, we have a public variable called games. Because it is public, it will be accessible via the HTML. It will contain all the games returned from the Node.js application.

When the page loads, we want to query our endpoint. It is never a good idea to do this in the constructor method, so, instead, we use the ionViewDidEnter method. After issuing the request, the result is transformed into JSON and then loaded into our public variable.

If we want to create a new game in our database, things are a little different. We are going to display a modal dialog and collect input.

public create() {
    let gameModal = this.modalCtrl.create(GamePage);
    gameModal.onDidDismiss(data = {
        let headers = new Headers({ "Content-Type": "application/json" });
        let options = new RequestOptions({ headers: headers });
        this.http.post("http://localhost:3000/game", JSON.stringify(data), options)
            .subscribe(result = {
                this.games.push({ "game_title": data.title, "console_title": ""});
            });
    });
    gameModal.present();
}

The create method will display our GamePage which will be in modal format. Any data entered in the form on the modal will be returned back to the GamesPage and sent via an HTTP request to the API.

Before we take a look at GamePage, let’s look at the HTML that powers GamesPage.

ion-header
    ion-navbar
        button ion-button menuToggle
            ion-icon name="menu"/ion-icon
        /button
        ion-titleGames/ion-title
        ion-buttons end
            button ion-button icon-only (click)="create()"
                ion-icon name="add"/ion-icon
            /button
        /ion-buttons
    /ion-navbar
/ion-header

ion-content padding
    ion-list
        button ion-item *ngFor="let game of games"
            {{game.game_title}}
span class="item-note"{{game.console_title}}/span
/button /ion-list /ion-content

In the above HTML, we are looping through our public games array. Each object in the array is rendered to the screen within a list. Angular does all the heavy lifting for us.

Open the project’s src/pages/game/game.ts file and include the following:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';
import { Http, Headers, RequestOptions } from "@angular/http";
import "rxjs/Rx";

@IonicPage()
@Component({
    selector: 'page-game',
    templateUrl: 'game.html',
})
export class GamePage {

    public consoles: Arrayany;
    public input: any;

    constructor(public navCtrl: NavController, public navParams: NavParams, public viewCtrl: ViewController, private http: Http) {
        this.consoles = [];
        this.input = {
            "cid": "",
            "title": ""
        }
    }

    ionViewDidEnter() {
        this.http.get("http://localhost:3000/consoles")
            .map(result = result.json())
            .subscribe(result = {
                for(let i = 0; i  result.length; i++) {
                    this.consoles.push(result[i]);
                }
            });
    }

    public save() {
        this.viewCtrl.dismiss(this.input);
    }

}

This modal logic is similar to what we’ve already seen. There will be a form that is bound to HTML and TypeScript. When the ionViewDidEnter triggers, we query for console information. This console information will eventually be used for a radio list that the user can select from.

When the user selects the save method, the data bound in the public form is passed to the previous GamesPage page.

The HTML that powers this modal, found in src/pages/game/game.html looks like this:

ion-header
    ion-navbar
    ion-titleNew Game/ion-title
    /ion-navbar
/ion-header

ion-content padding
    ion-list
        ion-item
            ion-label stackedTitle/ion-label
            ion-input type="text" [(ngModel)]="input.title"/ion-input
        /ion-item
        ion-item
            ion-labelConsole/ion-label
            ion-select [(ngModel)]="input.cid"
                ion-option *ngFor="let console of consoles" value="{{ console.id }}"{{ console.title }}/ion-option
            /ion-select
        /ion-item
        ion-item
            button ion-button full (click)="save()"Save/button
        /ion-item
    /ion-list
/ion-content

We have a simple list that makes up our form. The form elements are bound to our TypeScript variable and the console information is looped through to populate an HTML select element.

This brings us to the final page of the Angular frontend.

Open the project’s src/pages/consoles/consoles.ts file and include the following:

import { Component } from '@angular/core';
import { NavController, NavParams, AlertController } from 'ionic-angular';
import { Http, Headers, RequestOptions } from "@angular/http";
import "rxjs/Rx";

@Component({
  selector: 'page-consoles',
  templateUrl: 'consoles.html'
})
export class ConsolesPage {

    public consoles: Arrayany;

    public constructor(public navCtrl: NavController, private http: Http, private alertCtrl: AlertController) {
        this.consoles = [];
    }

    public ionViewDidEnter() {
        this.http.get("http://localhost:3000/consoles")
            .map(result = result.json())
            .subscribe(result = {
                this.consoles = result;
            });
    }

    public create() {
        let alert = this.alertCtrl.create({
            title: 'Add Console',
            inputs: [
                {
                    name: 'title',
                    placeholder: 'Title'
                },
                {
                    name: 'year',
                    placeholder: 'Year'
                }
            ],
            buttons: [
                {
                    text: 'Cancel',
                    role: 'cancel',
                    handler: data = {
                        console.log('Cancel clicked');
                    }
                },
                {
                    text: 'Save',
                    handler: data = {
                        let headers = new Headers({ "Content-Type": "application/json" });
                        let options = new RequestOptions({ headers: headers });
                        this.http.post("http://localhost:3000/console", JSON.stringify(data), options)
                            .subscribe(result = {
                                this.consoles.push(data);
                            }, error = {});
                    }
                }
            ]
        });
        alert.present();
    }

}

While not too different than what we’ve already seen, we have a new feature. We are using a popup dialog to collect input for new video game console information.

When the popup is dismissed, the following is executed:

handler: data = {
    let headers = new Headers({ "Content-Type": "application/json" });
    let options = new RequestOptions({ headers: headers });
    this.http.post("http://localhost:3000/console", JSON.stringify(data), options)
        .subscribe(result = {
            this.consoles.push(data);
        }, error = {});
}

This will take the information found in the form and send it via HTTP to our API which will, in turn, save the console information to the database.

Awesome right?

The HTML found in the project’s src/pages/consoles/consoles.html file looks like the following:

ion-header
    ion-navbar
        button ion-button menuToggle
            ion-icon name="menu"/ion-icon
        /button
        ion-titleConsoles/ion-title
        ion-buttons end
            button ion-button icon-only (click)="create()"
                ion-icon name="add"/ion-icon
            /button
        /ion-buttons
    /ion-navbar
/ion-header

ion-content
    ion-list
        button ion-item *ngFor="let console of consoles"
            {{ console.title }}
            span class="item-note" item-right
                {{ console.year }}
            /span
        /button
    /ion-list
/ion-content

Again, it is near identical to the other HTML files that we’ve seen.

Conclusion

You just got a recap of everything I went over at Midwest JS 2017. We saw how to create a Node.js API that communicates with Couchbase, our NoSQL database, as well as creating a frontend using Angular and Ionic Frameworks. These are just a few components of a full stack application.

For more information on using Node.js with Couchbase, check out the Couchbase Developer Portal. If you’d like me to come back to Midwest JS, let me know in the comments.

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*

cover letter