Hapi With Typescript

Build scalable applications with ease

Joseph Jude

Contents

- Preface

- 1 What this book covers

- 2 How this book is written

- 3 How this book is organized

- 4 Conventions

- 5 What you need to follow this book

- 6 Your feedback

- 7 Connect with me

- 8 Other books by me

- 9 Need help for your Hapi project?

- Chapter 1 Basics of Hapi & TypeScript

- 1.1 First Hapi app

- 1.2 Converting to TypeScript

- 1.3 Adding routes

- 1.4 Using plugins

- 1.5 Connecting to DB

- 1.5.1 Defining models

- 1.5.2 Reading and writing entities

- 1.5.3 Putting it all together

- 1.6 Summary

- Chapter 2 Introducing TypeScript

- 2.1 Why TypeScript?

- 2.2 Components of TypeScript

- 2.3 Installing and using TypeScript

- 2.4 Types for existing modules

- 2.5 TypeScript Basics

- 2.5.1 Comments

- 2.5.2 Declaration

- 2.5.3 Basic Types

- 2.5.4 Collections

- 2.5.5 Type Assertions

- 2.5.6 Control Flow

- 2.5.7 Functions

- 2.5.8 Classes

- 2.5.9 Interfaces

- 2.5.10 Decorators

- 2.5.11 Async, await

- 2.6 Automating your workflow with npm scripts

- 2.7 Summary

- Chapter 3 Routes, request, and reply

- 3.1 Basics of routing

- 3.2 Route Methods

- 3.3 Path Parameters

- 3.4 Optional parameters

- 3.5 Wildcard parameters

- 3.6 Route handlers

- 3.7 Query strings

- 3.8 Payload

- 3.9 Summary

- Chapter 4 Using SQL Databases

- 4.1 ORM and its usage

- 4.2 Introducing TypeORM

- 4.3 Initialization

- 4.4 Connecting to DB

- 4.5 Defining the models

- 4.6 Reading and writing entities

- 4.7 Putting it all together

- 4.8 Summary

- Chapter 5 Modularizing with plugins

- 5.1 Why modularize your application

- 5.2 Using a Hapi plugin

- 5.3 Composing with glue

- 5.4 Creating a plugin

- 5.5 Creating environment specific manifest

- 5.6 Summary

- Chapter 6 Validations with Joi

- 6.1 Schema validations with joi

- 6.2 Top level schema types

- 6.3 Validating Hapi routes

- 6.3.1 Validating query parameters & payloads

- 6.3.2 Validating responses

- 6.4 Summary

- Chapter 7 Testing using lab

- 7.1 Basics of testing

- 7.2 Getting started with lab and code

- 7.3 Testing Hapi code

- 7.4 Code coverage

- 7.5 Reporting

- 7.6 Summary

- Chapter 8 Supporting Resources

- 8.1 Hapidock, hapi inside docker

- 8.1.1 Objectives

- 8.1.2 Components

- 8.1.3 Usage

- 8.2 npm modules used in this book

Preface

1 What this book covers

Hapi.JS (called Hapi in this book) is a nodejs framework developed, originally, in Walmart Labs.  What’s so special about Hapi?  Let us hear from the man himself who developed it.  Eran Hammer developed Hapi and still serves as its lead contributor.  He wrote an introductory post describing Hapi (emphasis by Eran himself):

hapi was created around the idea that configuration is better than code, that business logic must be isolated from the transport layer, and that native node constructs like buffers and stream should be supported as first class objects.  But most importantly, it was created to provide a modern, comprehensive environment in which as much of the effort is spent delivering business value.

As emphasised by Eran, we, the software developers, should choose tools that aid us in delivering business value as quickly as possible.  Business owners want to experiment with ideas fast and move further with ideas that fetch maximum value.  This is why Hapi is attractive.

Eran and team used Hapi to power the e-commerce sites at Walmart, even during the peak black friday sale.  Hapi is a proven enterprise-scale nodejs framework.  If you are learning a nodejs framework, you should learn the battle-tested framework.

Though originally built at Walmart labs, a large community has developed around Hapi.  Hapi is now used to power e-commerce sites, fintech apps and so on.  As you can see from Hapi community page, both large and small companies use Hapi.

In this book we will also use another great developer tool–typescript.

Over the history of JavaScript there has been many attempts to modernize JavaScript.  The most recent and a successful one is an attempt by Microsoft.  TypeScript was introduced in October 2012, after two years of internal development at Microsoft.  TypeScript brings all the features lacking in JavaScript.  Yet, it does so without abandoning JavaScript.  TypeScript is just a superset of JavaScript.

So what are the benefits TypeScript brings to JavaScript?

1. TypeScript brings classes, and interfaces thereby bringing object oriented support;

2. TypeScript adds types to JavaScript, so errors are identified at compile time;

3. Because TypeScript brings static types, it has become easier to add intellisense and syntax checking to tools and IDEs;

4. Because of the same reason, it has become easier to refactor large codebase.

These features bring tremendous productivity gains for large teams with large code bases.

2 How this book is written

I’m writing this book in an incremental and iterative fashion, also called as agile method in the software development world.  I’m using softcover as a publishing platform to distribute the book.  You can read the book for free.  If you want the book in an e-book format or want the accompanying code, then you have to buy the book.  I’m also planning to create screencasts along with the book.

3 How this book is organized

Chapter 1: Basics of Hapi & TypeScript introduces all essential concepts of Hapi.  You will learn to handle urls using routes, connect to db to store and retrieve values, and use nunjucks templates.  You will get an overall view of Hapi with this chapter.

Chapter 2: Introduction to TypeScript, introduces all the concepts of typescript and how to compile TypeScript code automatically using npm scripts.

Chapter 3: Routes, request, and reply introduces how Hapi deals with incoming requests.

Chapter 4: Using SQL Databases introduces connecting to SQL dbs using TypeORM.

Chapter 5: Hapi plugin system introduces how to use Hapi plugin systems to modularize your Hapi project.

Chapter 6: Validating with Joi introduces joi plugin to validate route parameters.

Chapter 7: Testing Hapi introduces lab plugin to write automated test cases.

Chapter 8: Supporting resources introduces hapidock, a docker based container that you can use to quickly start developing Hapi applications.

4 Conventions

In this book, you will find a number of text styles that distinguish between different kinds of information.  Here are some examples of these styles and an explanation of their meaning.

A block of code is shown as below:

Listing 0.1:  Sample Code

1 server.start((err) => {

2 if (err) {

3  throw err;

4 }

5 console.log('Server running at:', server.info.uri);

6 });

In the course of the book, I will have to describe an ancillary topic.  Instead of breaking the flow, these ancillary topics will be listed within a box as below.  Same is true for important notes that I want you to remember.

Box 0.1.    NOTE Warnings or important notes appear in a box like this

Tips are also shown in boxes.

Box 0.2.    TIP Tips and tricks appear like this

5 What you need to follow this book

You need only 3 things besides this book: access to internet, a text editor, and a terminal application.

If you purchase the book, then you will get access to hapidock.  Hapidock is a definition file to download and setup all the essential components in one go.  If you do that, then you don’t need internet access on an ongoing basis.  Otherwise you will need internet access as you progress through the book.

There are so many choices for text editors and terminal applications in all the popular platforms.  I use Vim and Visual Studio Code.  Visual Studio Code comes with all batteries included.  If you have not yet developed a preference, I strongly recommend Visual Studio Code.

6 Your feedback

I welcome your feedback.  Please let me know your comments—what you liked and disliked.  Your feedback will help me improve this book.

Please send your feedback to feedback@jjude.com.

7 Connect with me

I write regularly at my blog about software development topics.  Subscribe, to learn as I write.

You can also connect with me on twitter at http://twitter.com/jjude.

8 Other books by me

Ionic 2: Definite Guide

Books I have written are listed at my site: https://jjude.com/books/.

9 Need help for your Hapi project?

- Using Hapi for your internal tools?

- A project that needs to be rescued?

You don’t have to do it alone.  I can help.

I can help you:

- Build your next MVP

- Testing

- Implement build and deployment automation

Get in touch with me at consulting@jjude.com.

Chapter 1

Basics of Hapi & TypeScript

This chapter gives you a comprehensive overview of Hapi.  After reading this chapter, you will know:

- how to write a simple server program in Hapi and TypeScript

- how to map Hapi functions to incoming requests, generally called as routes

- how to connect to SQL db

- how to use templates

This should be sufficient enough for you to understand Hapi.  Subsequent chapters will go into depth of each of these concepts.

1.1 First Hapi app

A simple server program in Hapi looks like this:

Listing 1.1:  First Hapi app

1 const hapi = require('hapi');

2

3 const server = new hapi.Server();

4 server.connection({port: 3000});

5

6 server.start((err) => {

7   if (err) {

8     throw err;

9   }

10   console.log('Server running at:', server.info.uri);

11 });

This is copy of the first tutorial from Hapi site.  If you run it with node ., it will run the server at port 3000.  However, if you open your browser to http://localhost:3000, it will throw an error, {"statusCode":404,"error":"Not Found"}.  That is because, this little program doesn’t know how to handle the incoming requests.

Let us modify the app so that the app displays “Hello World”.  The additional lines are highlighted in the modified program.

Listing 1.2:  Hello World in Hapi

1 const hapi = require('hapi');

2

3 const server = new hapi.Server();

4 server.connection({ port: 3000 });

5

6 server.route({

7   method: 'GET',

8   path: '/',

9   handler: function (request, reply) {

10     reply('Hello, world!');

11   }

12 });

13

14 server.start((err) => {

15   if (err) {

16     throw err;

17   }

18   console.log('Server running at:', server.info.uri);

19 });

This is the template for all Hapi programs.

1. require Hapi

2. Create a Hapi server using server.connection

3. Process incoming URLs via server.route

4. Start the server using server.start

1.2 Converting to TypeScript

TypeScript brings static type checking to JavaScript.  Let us re-write our program in TypeScript adding types to the variables.  Here is the same program in TypeScript.

Listing 1.3:  Hello World in TypeScript

1 "use strict";

2

3 import * as hapi from "hapi";

4

5 const server: hapi.Server = new hapi.Server()

6 server.connection({ port: 3000 });

7

8 server.route({

9   method: "GET",

10   path: "/",

11   handler: (request: hapi.Request, reply: hapi.IReply) => {

12     reply("Hello World")

13   }

14 });

15

16 server.start((err) => {

17   if (err) {

18     throw err;

19   }

20   console.log("server running at 3000");

21 })

In line #5, we are indicating to the compiler (and to other developers who has to use our code) that server is of type hapi.Server.  Similarly in line# 11, we are indicating that request is of type hapi.Request and reply is of type hapi.IReply.

This helps the IDEs as we code.  The IDEs (like Visual Studio Code and Vim), bring up intellisense to aid us coding.  Also the TypeScript compiler will throw up errors if we deviate from the definition of these variables.

From now on, all the programs in this book will be in TypeScript.  We will learn more about TypeScript in Chapter 2.

1.3 Adding routes

When we executed our first code Listing 1.1, we got an error {"statusCode":404, "error":"Not Found"}.  As we found out, that was because the code didn’t have a function mapped to the incoming request.  We solved that by a adding server.route method Listing 1.2.

In Hapi, server.route determines how a web-app should handle urls typed by a user.  A server.route has three elements: method, path, and handler.

Every http request contains a method (most popular are GET, POST, and DELETE).  You can map each of these methods with a single server.route or you can map multiple methods to a single server.route.

Path indicates the resource accessed.  Path can indicate a single resource or a collection.  When we type /questions/5, we are accessing a single question, whereas when we type /questions, we access all questions.

Handler function takes two parameters: request and reply.  The request parameter contains headers, authentication information, payloads and others.  The reply parameter is used to respond to the requests.  Usually it only contains payload.  But it can also have headers, content types, content length and so on.

Let us put all of these into a code snippet.

Listing 1.4:  Handling routes

1 server.route({

2   method: "GET",

3   path: "/",

4   handler: (request: hapi.Request, reply: hapi.IReply) => {

5     reply("This is a GET method")

6   }

7 });

8

9 server.route({

10   method: ["POST","PUT"],

11   path: "/new",

12   handler: (request: hapi.Request, reply: hapi.IReply) => {

13     reply("This is a " + request.method + " method")

14   }

15 });

16

17 server.route({

18   method: "GET",

19   path: "/questions/{id}",

20   handler: (request: hapi.Request, reply: hapi.IReply) => {

21     reply("Question requested is: " + request.params.id);

22   }

23 });

There are three blocks of server.route here.

The first code-block is a simple one.  Here, we handle only one http method, GET.  It serves root path / and reply with a message.

The second code-block handles both POST and PUT http methods.  It serves the urls at path /new.  It just prints type of incoming http method with request.method.  You can use request.method to decide the logic if some part of the code has to be different.

The third code-block serves a GET url with a parameter, id.  The parameter is sent back as a reply.

We will learn more about routes in Chapter 3.

1.4 Using plugins

Hapi is designed in a modularized manner.  It has all the essential features to build server applications.  Everything else is modularized into plugins.

Hapi has a plugin for templating, input validation, authentication, testing and so on.  We will learn of some of these plugins.  You can view all the available Hapi plugins in the Hapi website.  Some of these plugins are official plugins created by Hapi team.  There are also plugins created by the Hapi community.

You have to register a plugin before using it.  You register a plugin as below:

server.register(require("plugin"));

We will learn more about plugins in Chapter 5.

1.5 Connecting to DB

When it comes to databases, Nodejs is often associated with NoSQL dbs like Mongo.  Yet, SQL dbs like MySQL, Postgresql have been battle tested in real world scenarios for years.

In this book, we’ll learn how to connect to db from Hapi using TypeORM, a data mapper based ORM, to connect to db.  Specifically, we will connect to Postgresql.  The same concept can be used to connect to any relational db.

In Chapter 4, we will see how to install TypeORM. In this section, let us see how to use TypeORM to connect to db.

Using db involves, connecting to it, defining models (tables), writing into it, and reading from it.  Let us see each of these in this section.

Listing 1.5:  Connection parameters to connect to postgres db

1 import { createConnection } from "typeorm";

2

3 createConnection({

4   driver: {

5     type: "postgres",

6     host: "localhost",

7     username: "pg_user",

8     password: "pg_password",

9     database: "pg_dbname",

10     port: 5432

11   }

12 }).then(connection => {

13     console.log("db connected");

14   })

1.5.1 Defining models

In TypeORM, Entity decorator defines a table, Column decorator defines columns, and PrimaryGeneratedColumn defines a auto generated primary column.  Here is how you will define a blog post entity.

Listing 1.6:  Defining models in TypeORM

1 import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

2

3 @Entity()

4   export class Entry {

5

6     @PrimaryGeneratedColumn()

7       id: number;

8

9     @Column()

10       title: string;

11

12     @Column()

13       content: string;

14   };

TypeORM has to create or modify these tables in the db.  For that purpose, the defined models have to be part of the connection parameters.  The additional lines to include the defined models are highlighted.

Listing 1.7:  Including models in connection parameters

1 createConnection({

2   driver: {

3     type: "postgres",

4     host: "pg",

5     username: "postgres",

6     password: "secret",

7     database: "postgres",

8     port: 5432

9   },

10   entities: [

11     Entry

12   ]

13 }).then(connection => {

14   console.log("db connected and models created");

15 })

1.5.2 Reading and writing entities

TypeORM provides entityManager and repository to deal with entities.  TypeORM documentation recommends using repositories to connect to entities.  So we will use repositories.  The following snippet will fetch the corresponding repository and fetch all entries in the table.

Listing 1.8:  Fetching all entries

1 let entryRepo = connection.getRepository(Entry);

2 let allEntries = await entryRepo.find();

Now we can loop through allEntries and pick up individual entry for processing.  Similarly, entryRepo.persist(newEntry) persists a new entity.

1.5.3 Putting it all together

Now let us put the entire program together.  In the following snippet, we define two routes: a GET route that fetches all entries, and a POST route that creates a new entry.  We are using async, await for cleaner code.

Listing 1.9:  Using TypeORM to connect to db

1 "use strict";

2

3 import * as hapi from "hapi";

4 import "reflect-metadata";

5 import {createConnection, Entity, PrimaryGeneratedColumn, Column} from "typeorm";

6

7 const server: hapi.Server = new hapi.Server()

8 server.connection({ port: 3000 });

9

10 @Entity("Entries")

11 export class Entry {

12

13   @PrimaryGeneratedColumn()

14   id: number;

15

16   @Column()

17   title: string;

18

19   @Column()

20   slug: string;

21

22   @Column()

23   content: string;

24 };

25

26 server.route({

27   method: "GET",

28   path: "/",

29   handler: async (request: hapi.Request, reply: hapi.IReply) => {

30     const entryRepo = server.app.dbConnection.getRepository(Entry);

31     const entries = await entryRepo.find();

32     reply(entries);

33   }

34 });

35

36 server.route({

37   method: "POST",

38   path: "/",

39   handler: async (request: hapi.Request, reply: hapi.IReply) => {

40

41     let {title,slug,content} = request.payload;

42

43     let newEntry = new Entry();

44     newEntry.title = title;

45     newEntry.slug = slug;

46     newEntry.content= content;

47

48     let entryRepo = server.app.dbConnection.getRepository(Entry);

49     await entryRepo.persist(newEntry);

50

51     reply("New post created");

52   }

53 });

54

55 createConnection({

56   driver: {

57     type: "postgres",

58     host: "pg",

59     username: "postgres",

60     password: "secret",

61     database: "postgres",

62     port: 5432

63   },

64   entities: [

65     Entry

66   ]

67 }).then(async connection => {

68   server.start(err => {

69     if (err) {

70       throw err;

71     }

72  // set the connection to server.app so it can be used in methods

73  server.app.dbConnection = connection;

74     console.log("server running in " + server.info.uri);

75   })

76 })

77 .catch(err => console.log(err));

Here all activities, defining models, connecting to db, defining routes, and bringing up the server, all happens within the same file.  This is not a recommended practice.  In the coming chapters, we will modularize them.

1.6 Summary

- Hapi is a nodejs framework to build web applications and backend servers;

- Hapi is a modularized framework with rich set of plugins;

- TypeScript brings static type checking to JavaScript;

- Create a Hapi server with server.connection and server.start;

- Use server.route to map incoming urls to handling functions;

- server.route has three components: method, path, and handler;

- vision is the plugin to connect templates to Hapi

- We can use TypeORM to connect to SQL db;

Table of Contents

Frontmatter

Chapter 1