Node.Js: Eine kleine Demo mit Express, TypeScript, Angular und MongoDB Teil 1

Dieser Artikel hat mehrere Teile und soll alle notwendigen Schritte zeigen, wie man eine kleine Applikation unter Node.JS zum laufen bekommt.

Auf die einzelnen Schritte, wie man Software hierbei installiert, wird hierbei verzichtet, teilweise werde ich auf einen eigenen Artikel, falls ich es für notwendig erachte oder falls dies gewünscht wird. Bitte in diesen Fall als Kommentar mitteilen.

Vorbereitung

Als Editor verwende ich hier Visual Studio Code, aber es sind natürlich auch andere Editoren, wie beispielsweise Brackets, Sublime, WebStorm ebenso einsetzbar.(Download von Visual Studio Code ist unter https://code.visualstudio.com möglich)

Zusätzlich ist noch Node.JS zu installieren, hier habe ich die aktuelle LTS 4.4.5 verwendet. (Download unter https://nodejs.org)

Projekt Ordner anlegen

Lege nun einen neuen Ordner bei sich an:

Anschließend öffnest du Visual Studio Code und wählst den Punkt Ordner öffnen...:

Wählen bitte anschließend den Arbeitsordner aus:

Anschließend wird angezeigt, dass der Ordner noch leer ist:

Öffne nun die Eingabeaufforderung:

Führe jetzt "npm init" aus, damit wir die initiale Paketkonfiguration für NodeJs generieren können.

Anschließend haben wir die "package.json" Datei erzeugt, die unsere Pakete definiert:

ExpressJS als WebServer vorbereiten

Als nächstes Installieren wir ExpressJS (https://de.wikipedia.org/wiki/Express.js). Es handelt sich hier um ein vielseitiges JavaScript Application Framework, dass verwendet werden kann, um uns WebSeiten usw. anzuzeigen.:

Sollte die Installation nicht klappen, ist möglicherweise der Proxy nicht richtig konfiguriert für NodeJs. Hier meine Anleitung zu diesem Problem. (http://blog.ninja4.net/visual-studio-code-und-npm-konfigurieren-eines-proxy-hinter-firewall/)

Sollte die Installation geklappt haben, dann existiert jetzt ein "node_module", dass den installierten Code für Express anzeigt. Die Package.json zeigt nun auch an, dass es eine Abhängigkeit zu express gibt. (Hinweis: Ich hatte Probleme beim ersten mal und musste noch einmal "npm init" und "npm install express" eingeben, damit package.json aktualisiert wurde).

Nun können wir eine neue Datei anlegen und den folgenden Code installieren (Quelle: http://expressjs.com/en/starter/hello-world.html).

var express = require('express');  
var app = express();

app.get('/', function (req, res) {  
  res.send('Hello World!');
});

app.listen(3000, function () {  
  console.log('Example app listening on port 3000!');
});

Anschliessend speichern wir die Datei unter dem Namen "Index.js":

Nun können wir den Webserver über die Eingabeaufforderung starten:

Möglicherweise erscheint eine Warnung von der Firewall, hier bitte dann zulassen:

Anschließend können wir mit dem Browner via http://localhost:3000 bereits unsere Applikation ansehen:

Das ist natürlich noch nicht spektakulär aber immer ein Anfang.

TypeScript verwenden

Nun läuft Express aber noch als JavaScript Applikation, aber eigentlich sollte dies bereits in TypeScript erfolgen. (https://de.wikipedia.org/wiki/TypeScript)

TypeScript ist aber keine Programmiersprache die NodeJs direkt kann, diese muss konvertiert werden.

Zunächst müssen wir TypeScript installieren. Das geht wieder mit dem npm aus der Eingabeaufforderung:

Als nächstes müssen wir das Hilfsprogramm Typings installieren, dass als Paketmanager für TypeScript dient:

Nun können wir die Definitionsdateien für Express und Node installieren. Das ist notwendig, da Express in JavaScript ist und wir Definitionsdateien benötigen, damit diese Funktionen uns in TypeScript als Klassen zur Verfügung gestellt werden können:

Anschließend existiert eine neue Typings.json Datei und ein Typing Verzeichnis:

Die Typings.json Datei enthält die Referenzen auf die verwendeten TypeScript Module:

Das Verzeichnis typings enthält einen Ordner "Global" wo wir unsere Pakete installiert haben und eine index.d.ts, die alle Module einbindet:

Als nächstes Erstellen wir eine TypeScript Datei "index.ts" und geben folgenden Code ein:

/// <reference path="typings/index.d.ts" />
"use strict";

import * as express from "express";

class Server {  
    public app: express.Application;

    public static bootstrap(): Server {
        return new Server();
    }

    constructor() {
        this.app = express();
        this.app.get('/', function (req, res) {
            res.send('Hello World!');
        });

        this.app.listen(3000, function () {
            console.log('Example app listening on port 3000!');
        });
    }

}

Server.bootstrap();  

Durch die Reference geben wir das generierte "index.d.ts" an, dass uns das Notwendige express Objekt generiert.

/// <reference path="typings/index.d.ts" />

Wie wir weiter oben gesehen hat, enthält die index.d.ts eine Rereference auf "globals/express/index.d.ts":

/// <reference path="globals/express/index.d.ts" />

Diese Datei deklariert dann das Module "express":

// Generated by typings
// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/express/express.d.ts
declare module "express" {  
    import * as serveStatic from "serve-static";
    import * as core from "express-serve-static-core";

    /**
     * Creates an Express application. The express() function is a top-level function exported by the express module.
     */
    function e(): core.Express;

    namespace e {

... usw. ...

In unserer Datei index.ts importieren wir dann dieses Modul:

import * as express from "express";  

Hiermit bietet uns nun Intellisense die möglichen Methoden und Attribute an:

Nachdem das Code eingeben wurde und gespeichert wurde, können wir nun mit den Befehl tsc daraus ein JavaScript generieren:

Die generierte Datei sieht der ursprünglichen Datei recht ähnlich aus:

Diese können wir jetzt wieder mit Node starten und uns das Ergebnis ansehen!

Automatisieren mit Grunt

Jetzt ist es recht Zeit- und Fehlerträchtig, jedes mal "tsc" aufzurufen und im laufe der Entwicklung werden wir noch weitere Konverter verwenden. Ich möchte in diesen Beispiel "Grunt" verwenden, um möglichst unabhängig von Editor usw. zu bleiben. Selbstverständlich gibt es auch andere Lösungen.

Mit dem Node Packet Manger installieren wir "grunt-ts" und natürlich auch "grunt":

Installieren von "grunt" als globalen Befehl:

Damit wir Grunt nützen können, muss ein Gruntfile.js angelegt werden:

Diese Datei befüllen wir mit diesem JavaScript:

module.exports = function (grunt) {

    grunt.initConfig({

    ts: {
        base: {
        src: ['src/typescript/**/*.ts'],
        dest: 'dist/js',
        options: {
            module: 'commonjs', 
            target: 'es5',
            sourceMap: true,
            declaration: true
        }
        }
    }

    });

    grunt.loadNpmTasks('grunt-ts');
}

Wir sagen damit Grunt er soll alles aus dem Verzeichnis "src/typescript" mit der Bezeichnis *.ts bitte konvertieren und in "dist/js" kopieren.

Damit das funktioniert, müssen wir die Verzeichnise anlegen und die Datei index.ts dort hin verschieben. Die Reference zu den Typings müssen wir auch anpassen:

Nun können wir grunt ausprobieren:

Ist alles richtig gelaufen, dann existiert jetzt im Verzeichnis "dist/js" neben anderen Dateien auch die "index.js":

Eine Automatisierung haben wir jetzt aber immernoch nicht. Das müssen wir Grunt nun mit watch beibringen.

Langsam gewöhnen wir uns daran und installieren nun mit den Packetmanager grunt-contrib-watch:

Mit dem Befehl "grunt.loadNpmTasks('grunt-contrib-watch');" läd man den Watch in das Grunt Objekt.

Die Config muss zudem so erweitert werden, dass wenn *.ts files geändert werden, dann immer automatisch der ts Task gestartet wird.

Das Gruntfile.js sieht dann wie folgt aus:

module.exports = function (grunt) {

    grunt.initConfig({

    ts: {
        base: {
        src: ['src/typescript/**/*.ts'],
        dest: 'dist/js',
        options: {
            module: 'commonjs', 
            target: 'es5',
            sourceMap: true,
            declaration: true
        }
        }
    },

    watch: {
      ts: {
        tasks: ['ts'],
        files: ['src/typescript/**/*.ts']
      }
    }

    });

    grunt.loadNpmTasks('grunt-ts');
    grunt.loadNpmTasks('grunt-contrib-watch');
}

Nun sollte am besten eine weitere Eingabeaufforderung starten, die zukünftig für den "Grunt-Watch" verwendet wird. In dieser führt man dann den Befehl "grunt watch" aus:

Nun ändern wir im Hello World die Anzahl der Ausrufungszeichen:

Nachdem wir speichern läuft der Grunt Task automatisch los:

Und auch das Ziel hat jetzt mehr Ausrufungszeichen erhalten:

Grunt automatisch Server reload

Als nächstes soll das Starten automatisiert werden. Hier gilt es auch zu überlegen, die "index.ts" endlich umzubenennen, so dass es den Klassennamen enthält also in "server.ts":

Wenn der Grunt Watch noch läuft, generiert er neue JS Files... Allerdings löscht er nicht die alten index.js Dateien, diese sind manuell zu löschen:

Ins Root Verzeichnis stellen wir ein kurzes "index.js" das nur unser Server Objekt aufruft:

Dies lässt sich nun, wie gewohnt starten:

Zunächst müssen wir Grunt beibringen, dass er unseren Server starten kann, hierfür verwenen wir nodemon.

Installation von nodemon erfolgt nach gewohnten Schema:

Anschliessend können wir testen, ob unsere Applikation mit nodemon startet:

Damit Nodemon auch von Grunt verwendet werden kann, müssen wir das Modul installieren:

Nun können wir das Gruntfile für Nodemon erweitern:

module.exports = function (grunt) {

    grunt.initConfig({

    ts: {
        base: {
        src: ['src/typescript/**/*.ts'],
        dest: 'dist/js',
        options: {
            module: 'commonjs', 
            target: 'es5',
            sourceMap: true,
            declaration: true
        }
        }
    },

    nodemon: {
        dev: {
            script: 'index.js'
        }        
    },

    watch: {
      ts: {
        tasks: ['ts'],
        files: ['src/typescript/**/*.ts']
      }
    }

    });

    grunt.loadNpmTasks('grunt-ts');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-nodemon');
}

Nun können wir Nodemon auch mit Grunt starten:

Schön wäre es natürlich, wenn Grunt gleich zwei Tasks starten könnte, nämlich "watch" und "nodemon". Auch das ist kein Problem. Selbstverständlich gibt es auch hier ein module "grunt-concurrent":

Jetzt kann Grunt schon folgendes:

Wir ändern unser TypeScript File und entfernen das "Hello World" in "Hallo Welt das macht Grunt automatisch!".

Nachdem wir speichern und die Webseite *manuell neu laden, sehen wir das der Text sich auch dort geändert hat:

Das tüpfelchen auf dem "i" wäre es jetzt noch, wenn auch der Browser neu laden würde.

Auch das geht!

Hierzu ist nur im Watch ein Livereload Eintrag zu machen!

module.exports = function (grunt) {

    grunt.initConfig({

    ts: {
        base: {
        src: ['src/typescript/**/*.ts'],
        dest: 'dist/js',
        options: {
            module: 'commonjs', 
            target: 'es5',
            sourceMap: true,
            declaration: true
        }
        }
    },

    nodemon: {
        base: {
            script: 'index.js'
        }        
    },

    concurrent: {
        base: {
            tasks: ['watch','nodemon']
        }
    },

    watch: {
      ts: {
        tasks: ['ts'],
        files: ['src/typescript/**/*.ts']
      },
      livereload: {
        options: { livereload: true },
        files: [ 'public/**/*.{css,js,html}']
      }
    }

    });

    grunt.loadNpmTasks('grunt-ts');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-nodemon');
    grunt.loadNpmTasks('grunt-concurrent');
}

Zusätzlich muss man noch in Chrome den LiveReload installieren: (https://chrome.google.com/webstore/detail/livereload)

Jetzt Grunt neu startet mit "concurrent".

Im Chrome den Livereload aktivieren:

Und dann wird die Webseite bei Code Änderungen automatisch aktualisiert:

Zusammenfassung

Mit dieser Demo wurden schon mehrere Grundbausteine zusammengeführt. Die Entwicklungsumgebung lokal steht. Es steht ein Webservice durch Express zur Verfügung. Die Entwicklung lässt sich mit TypeScript fortführen. Mit Grunt ist die automatische Paketierung, Generierung sichergestellt, diese lässt sich nun Stückweise erweitern.

Über Feedback würde ich mich freuen.