A Modern MEAN-stack with Angular and Typescript (Part 1)

Photo by Tolu Olubode on Unsplash

Introduction

Prerequisites

Set up an empty Angular project

$ ng new NameApp --create-application=false --new-project-root=. --skip-install=true
$ cd NameApp
$ ng generate application client --skip-install=true

Install some packages to support Express

$ npm install express
$ npm install --save-dev @types/express
$ mkdir -p server/bin
$ touch server/app.ts
$ touch server/bin/www
#!/usr/bin/env node/**
* Module dependencies.
*/
const app = require('../app').default();
const debug = require('debug')('NameApp:server');
const http = require('http');
/**
* Get port from environment and store in Express.
*/
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
const server = http.createServer(app);/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port, () => console.log(`Application is listening on port ${ port }`));
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
const port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
const addr = server.address();
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
import * as express from 'express';
import { Express, Request, Response } from 'express';
export default function createApp(): Express {
const app = express();
app.get('/api/:name', async (req: Request, res: Response) => {
const name = req.params.name;
const greeting = { greeting: `Hello, ${ name }` };
res.send(greeting);
});
return app;
}

Setting up a build artifacts directory

/dist
- /api
- /bin/www
- app.js
# (... everything we made with Express ...)
- /public
# (... everything we made with Angular)
{
// ...
"projects": {
"client": {
"architect": {
"build": {
"options": {
"outputPath": "dist/public", // <--- CHANGE THIS PATH
$ touch ./server/tsconfig.api.json
{
"compilerOptions": {
"baseUrl": "../",
"module": "CommonJS",
"resolveJsonModule": false,
"esModuleInterop": false,
"target": "ESNext",
"outDir": "../dist/api",
"sourceMap": true,
"types": [
"node"
],
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
$ mkdir -p ./dist/api/bin
$ cp ./server/bin/www ./dist/api/bin
$ tsc -p ./server/tsconfig.api.json
$ node ./dist/api/bin/www
$ curl http://localhost:3000/api/Artie

Setting up scripts for unified client and server-side development

import * as express from 'express';
import * as path from 'path'; // < -- add this
import { Express, Request, Response } from 'express';
export default function createApp(): Express {
const app = express();
const clientDir = path.join(__dirname, '../public'); // <-- add this
app.use(express.static(clientDir)); // <-- and add this
app.get('/api/:name', async (req: Request, res: Response) => {
const name = req.params.name;
const greeting = { greeting: `Hello, ${ name }` };
res.send(greeting);
});
return app;
}
$ ng build
$ tsc -p ./server/tsconfig.api.json
$ node ./dist/api/bin/www
{
// ...
"scripts": {
//...
"clean": "rm -rf ./dist/api && rm -rf ./dist/public/",
"cp:www": "mkdir -p ./dist/api/bin && cp ./server/bin/www ./dist/api/bin/",
"dev": "concurrently -k \"tsc -p ./server/tsconfig.api.json -w\" \"cd ./dist/api && nodemon -r ./bin/www --watch\" \"ng build --watch\""
}
}

Bonus: Refresh the browser when client-side code changes

$ npm install --save-dev livereload connect-livereload
import * as livereload from 'livereload';
import * as connectLivereload from 'connect-livereload';
const app = express();
const clientDir = path.join(__dirname, '../public');
// In development, refresh Angular on save just like ng serve does
let livereloadServer: any;
if (process.env.NODE_ENV !== 'production') {
livereloadServer = livereload.createServer();
livereloadServer.watch(clientDir);
app.use(connectLivereload());
livereloadServer.once('connection', () => {
setTimeout(() => livereloadServer.refresh('/'), 100);
});
}
$ touch ./server/decs.d.ts
declare module 'livereload';
declare module 'connect-livereload';

Putting it all together

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
template: `
<div class="app-container" style="width:20rem; margin: 2rem auto;">
<div class="form-group" >
<label for="name-input">Enter a name:</label>
<input class="form-control" id="name-input" required [(ngModel)]="nameInput">
<button class="btn btn-primary"(click)="greetMe()">Greet Me</button>
</div>
<div class="name-display">
<p *ngIf="responseDisplay && responseDisplay.length > 0">
{{ responseDisplay }}
</p>
</div>
</div>
`
})
export class AppComponent {
constructor(private http: HttpClient) { } nameInput: string = '';
responseDisplay: string = '';
greetMe(): void {
this.http.get(`/api/${ this.nameInput }`)
.subscribe((response: any) => this.responseDisplay = response.greeting);
}
}
/* You can add global styles to this file, and also import other style files */
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');

Conclusion

I am a second-career software engineer currently working in the public sector.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store