facebook

Blog

Resta aggiornato

Un semplice startup project per creare una applicazione Angular che usa NodeJS e Typescript per il backend
Angular, NodeJS e Typescript insieme
mercoledì 24 Ottobre 2018

Un nuovo cliente mi ha chiamato per un progetto su un dispositivo embedded, dove l’obiettivo principale è fornire una semplice user interface per controllare l’esecuzione  di un workflow sul device. Dopo aver scartato Asp.Net Core, perché attualmente non supporta i processori ARM, abbiamo proposto Angular per il front-end e Node per il backend.

Ovviamente, l’esecuzione del workflow è demandata ad un progetto core scritto in C++: il nostro lavoro consiste nella realizzazione della user interface, per creare e visualizzare il workflow.

Il cliente ha accettato l’uso di Node ma, per la sua conoscenza della piattaforma .Net, ci ha chiesto di usare Typescript anche per il backend. Nessun problema, naturalmente, ma ho pensato fosse interessante condividere il setup di un progetto Angular con Node e Typescript.

Come primo passo installiamo i prerequisiti:

  • NodeJS da https://nodejs.org/
  • Angular CLI con il comando npm install -g @angular/cli
  • Typescript con il comando npm install -g typescript
  • Gulp con il comando npm install -g gulp

Creiamo un nuovo progetto con Angular CLI (per esempio con il comando ng new angular-node-typescript), e apriamo il progetto con il nostro code editor preferito, nel mio caso Visual Studio Code: https://code.visualstudio.com.

Creiamo una cartella “server” nella root del progetto, dove metteremo il codice del backend, e creiamo in questa cartella un nuovo file chiamato server.ts. Come framework per il backend NodeJS, usiamo ExpressJS, installabile nel progetto con i seguenti comandi:

  • npm install –save express per ExpressJS, l’opzione –save aggiunge il pacchetto richiesto alla nostra configurazione nel package.json
  • npm install –save body-parser come middleware ExpressJS. Il suo compito è estrarre l’intero contenuto del body della richiesta ed esporla in req.body. Molto utile.
  • npm install @types/node @types/body-parser @types/express -D, per le definizioni dei tipi per Typescript di Node, body-parser e Express. L’opzione -D salva le dipendenze come dipendenze di sviluppo, perché sono utili solo durante la fase di sviluppo (nel package.json troveremo queste dipendenze nel blocco devDependencies)

Ritorniamo al file server.ts e importiamo le librerie necessarie:

import * as express from 'express'; 
import * as path from 'path'; 
import * as http from 'http'; 
import * as bodyParser from 'body-parser';

Possiamo ora creare la classe Server:

class Server { 
    public app: express.Application; 
 
    public static bootstrap(): Server { 
        return new Server(); 
    } 
 
    constructor() { 
        // create expressjs application 
        this.app = express(); 
    } 
}

E chiamare il metodo statico bootstrap per la creazione dell’oggetto Server:

Server.bootstrap();

Adesso possiamo inizializzare il nostro web server, creando un metodo privato di questa classe, che chiameremo config.Qui configureremo il middleware body-parser, setteremo la cartella pubblica dove pubblicheremo il progetto Angular, configureremo la porta dove il server ascolterà le richieste HTTP e faremo partire il server:

private config() { 
    // Parsers for POST data 
    this.app.use(bodyParser.json()); 
    this.app.use(bodyParser.urlencoded({ extended: false })); 
 
    // Point static path to public folder 
    this.app.use(express.static(path.join(__dirname, 'public'))); 
  
    /** 
     * Get port from environment and store in Express. 
     */
    const port = process.env.PORT || '3000'; 
    this.app.set('port', port); 
 
    /** 
     * Create HTTP server. 
     */
    const server = http.createServer(this.app); 
 
    /** 
     * Listen on provided port, on all network interfaces. 
     */
    server.listen(port, () => console.log(`API running on localhost:${port}`)); 
}

Il nostro backend è una collezione di API REST, quindi dobbiamo configurare le rotte del progetto. Per questo scopo, creeremo un secondo metodo privato nella classe Server, che chiameremo routes:

private routes() { 
    // get router 
    let router: express.Router; 
    router = express.Router(); 
 
    // create routes 
    const api: BackendApi = new BackendApi(); 
 
    // test API 
    router.get('/api/test', api.test.bind(api.test)); 
 
    // use router middleware 
    this.app.use(router); 
 
    // Catch all other routes and return the index file 
    this.app.get('*', (req, res) => { 
        res.sendFile(path.join(__dirname, 'public/index.html')); 
    }); 
}

Come potete vedere, nel codice usiamo il middleware router di ExpressJS per migliorare la manutenibilità del progetto. Metteremo le implementazioni delle API in una sottocartella chiamata routes. Per il nostro esempio, creeremo un file backendapi.ts in questa cartella:

import * as express from 'express'; 
 
export class BackendApi { 
    public test(req: express.Request, res: express.Response) { 
        res.json('Hello World'); 
    } 
}

Giusto una nota sull’ultima istruzione del metodo routes:

// Catch all other routes and return the index file 
this.app.get('*', (req, res) => { 
    res.sendFile(path.join(__dirname, 'public/index.html')); 
});

Questo permette di rispondere con l’applicazione Angular buildata su tutte le richieste non gestite diversamente. Quindi, il costruttore del Server diventa il seguente:

constructor() { 
    // create expressjs application 
    this.app = express(); 
 
    // configure application 
    this.config(); 
 
    // configure routes 
    this.routes(); 
}
constructor() { 
    // create expressjs application 
    this.app = express(); 
 
    // configure application 
    this.config(); 
 
    // configure routes 
    this.routes(); 
}

Il server è pronto, quindi, se ci spostiamo nella cartella server ed eseguiamo il comando tsc server.ts, il compilatore Typescript crea il file server.js, che, eseguito in Node con il comando node server.js, fa partire il nostro server:

Se apriamo il browser all’indirizzo http://localhost:3000/api/test, vedremo il risultato giusto:

Adesso possiamo modificare l’applicazione Angular per chiamare il servizio. In app.module.ts, presente nella cartella src/app, importiamo il modulo HTTP client:

import { BrowserModule } from '@angular/platform-browser'; 
import { NgModule } from '@angular/core'; 
import { HttpClientModule } from '@angular/common/http'; 
import { AppComponent } from './app.component'; 
 
@NgModule({ 
    declarations: [ AppComponent ], 
    imports: [ BrowserModule, HttpClientModule ], 
    providers: [], 
    bootstrap: [AppComponent] 
}) 
export class AppModule { }

In app.component.ts, usiamo HTTP client per chiamare il servizio:

import { Component } from '@angular/core'; 
import { HttpClient } from '@angular/common/http'; 
 
@Component({ 
    selector: 'app-root', 
    templateUrl: './app.component.html', 
    styleUrls: ['./app.component.css'] 
}) 
export class AppComponent { 
    title = 'app'; 
 
    constructor(httpClient: HttpClient) { 
        httpClient.get('http://localhost:3000/api/test')
            .subscribe(arg => this.title = arg); 
    } 
}

Ovviamente questo è solo un esempio: non chiameremo mai un servizio direttamente da un componente, giusto?

Lanciando l’applicazione Angular con il comando ng serve -o, possiamo vedere il risultato nel browser:

Ok, funziona. Ma se dobbiamo lavorare su un progetto reale, è importante automatizzare i task necessari ad eseguire e testare la nostra applicazione: quindi configuriamo Gulp per eseguire tutti i task necessari. Gulp è un famoso task runner, molto semplice da usare perché può essere configurato usando JavaScript. Tutta la documentazione la trovate sul sito ufficiale: https://gulpjs.com/

Installiamo Gulp nel progetto con il comando npm install gulp -D. Abbiamo anche bisogno di alcuni plugin di Gulp, uno per la compilazione Typescript, gulp-typescript, e per l’esecuzione del server Node, gulp-nodemonnpm install gulp-typescript gulp-nodemon -D.

Adesso creiamo nella root del progetto un file chiamato gulpfile.js, il file di configurazione di Gulp, e cominciamo a importare le dipendenze:

var gulp = require('gulp'), 
    ts = require('gulp-typescript'), 
    exec = require('child_process').exec, 
    nodemon = require('gulp-nodemon'); 

L’oggetto exec sarà utile per lanciare i comandi di Angular CLI. Creare un task in Gulp è molto semplice, dobbiamo solo chiamare il metodo task con due parametri: il nome del task e la funzione che sarà eseguita quando il task sarà invocato con il suo nome. Per esempio, per la compilazione Typescript del codice di backend, scriveremo:

gulp.task('backend', function () { 
    return gulp.src('./server/**/*.ts')
        .pipe(ts({ noImplicitAny: true, lib: ["es2015"] 
    }))
    .pipe(gulp.dest('dist/')); 
});

Il task si chiama ‘backend’, e nella funzione di callback prendiamo tutti i file della cartella server e li serviamo, con la funzione pipe, al compilatore Typescript, spostando il risultato della compilazione nella cartella chiamata dist. Adesso, se scriviamo nella finestra del terminale il comando gulp tsc, il task viene invocato ed eseguito.

Adesso creiamo un nuovo task, chiamato frontend, nel file di configurazione di Gulp per la build dell’applicazione Angular:

gulp.task('frontend', function (cb) { 
    exec('ng build --prod -op=dist/public', function (err, stdout, stderr) { 
        console.log(stdout); 
        console.log(stderr); 
        cb(err); 
    }); 
})

Quindi, se eseguiamo nel terminale il comando gulp frontend, l’applicazione Angular sarà buildata nella cartella dist/public. È il momento di eseguire l’applicazione, quindi creiamo un nuovo task chiamato ‘nodemon’:

gulp.task('nodemon', function () { 
    nodemon({ 
        script: 'dist/server.js', 
        ext: 'js', 
        env: { 'NODE_ENV': 'development' } 
    }) 
});

Nodemon è un monitor NodeJS: esegue lo script dist/server.js e fa ripartire il server Node se il file cambia. Nel teminale, eseguiamo gulp nodemon per far partire l’applicazione:

Adesso il nostro frontend è disponibile sulla stessa porta del backend perché viene servito dal backend:

Ma noi siamo perfezionisti, quindi creiamo un nuovo task Gulp per ricompilare il backend se i file Typescript cambiano:

gulp.task('watch', ['backend'], function () { 
    gulp.watch('./server/**/*.ts', ['backend']); 
});

E, dato che se creiamo un task con il nome default, possiamo richiamarlo con il comando gulp senza parametri, possiamo creare l’ultimo task per richiamare tutti i task già creati:

gulp.task('default', ['frontend', 'backend', 'watch', 'nodemon']);

In questo modo, dobbiamo solo eseguire il comando gulp nel terminale! Tutto il codice è disponibile sul mio GitHub: https://github.com/apomic80/angular_node_typescript  

A presto