Build an API with Hapi.js
Hello! I’m Ryan
2
@ryanchenkie
chenkie
WHAT WE’LL BUILD
3
We’ve got an Angular app to manage FEM instructors
Now we need an API
4
LET’S MAKE AN API
5
6
What we’ll need
7
8
GET THE ANGULAR APP
9
GET THE HAPI API
1.
What is Hapi.js All About?
10
Hapi.js
“A rich framework for building applications and services”
11
Hapi.js History
What is Hapi.js used for?
12
Not the most popular kid
13
HOW DOES HAPI STACK UP?
Error objects, validation, sessions, CORS, logging
14
WHY HAPI?
The Hapi.js Ecosystem
Error Objects
Validation
HTTP Client
Utilities
Process Monitoring
15
What does a Hapi.js app look like?
16
Wiring up a server is dead simple
17
CREATE A SERVER INSTANCE
const Hapi = require('hapi');
const server = new Hapi.Server();
To make it do something, define a route
18
DEFINE A ROUTE
server.route({� method: 'GET',
path: '/api/stuff',
handler: (request, reply) => { ... }
});
Tell Hapi which port to listen on and start the server
19
START THE SERVER
server.connection({ port: 3001 });
server.start(err => {
if (err) throw err;
});
Send a response to the client using reply
20
THE REPLY INTERFACE
...
handler: (request, reply) => {
reply('Hey there');
}
Challenge #1
WIRE UP A SIMPLE APP
21
2.
Routes and Route Handlers
22
When it comes down to it, the web is all about request and response
23
REST API Principles Review
GET, POST, PUT, PATCH, DELETE
A FEW CORE PRINCIPLES
24
REST API Principles Review
OPERATIONS ON A SINGLE RESOURCE
Given some contacts resource, we might want multiple endpoints to have full CRUD operations
25
GET /contacts
GET /contacts/42
POST /contacts
PUT /contacts/42
DELETE /contacts/42
At a minimum, a route needs three things:�method, path, handler
Routes are configured with an options object
26
ROUTE CONFIGURATION
27
�� server.route({� method: 'GET',
path: '/api/stuff',
handler: (request, reply) => { ... }
});
When we send a request to an endpoint, we want some action to take place
The route handler is where the magic happens
Example: save to a database, calculate a value, fetch an external resource
28
THE ROUTE HANDLER
For the route handler to do its job, it uses a request object and a reply interface
29
THE ROUTE HANDLER
The Request Object
For each incoming request, a request object is created
The request object has useful information about the request
30
GET /api/contacts/42?profile=true
31
DATA ON THE REQUEST OBJECT
path: '/api/contacts/{id}'
handler: (request, reply) => {
const id = request.params.id; // 42
const profile = request.query.profile // true
}
The Reply Interface
A function argument to respond to requests
Used as a callback interface and a response generator depending on where it is used
32
Send a response to the client using reply
33
THE REPLY INTERFACE
...
handler: (request, reply) => {
reply('Hey there');
}
Challenge #2
RETURN SOME DATA
34
Challenge #2
NOTES
35
Challenge #2
BONUS
36
3.
Routes Prerequisites
37
APIs don’t operate in a vacuum
38
Regardless of the task, it needs to complete before a reply can be sent to the client
39
COMMON API TASKS
Typical Approach:�Everything in the Handler
40
41
path: '/api/contacts/{id}',
handler: (request, reply) => {
getContactData({ id },(err, data) => {
getContactPhoto({ id }, (err, data) => {
getOtherInfo((err, data) => {
// on and on
});
});
});
}
42
path: '/api/contacts/{id}',
handler: (request, reply) => {
getContactData({ id }).then(data => {
return getContactPhoto({ id });
}).then(data => {
return getOtherInfo();
}).then(data => {
// other tasks
}).catch(err => ...);
}
A better way: specify some action to happen before the
route handler is reached
43
ROUTE PREREQUISITES
What does a route prerequisite look like?
44
ROUTE PREREQUISITES
45
config: {
pre: [
{ method: query.verifyUniqueEmail },
{ method: query.createContactSlug, assign: 'slug' }
],
handler: (request, reply) => {
reply(request.pre.slug);
}
}
46
const verifyUniqueEmail = (request, reply) => {
const email = request.payload.email;
if (!emailExists(email)) {
reply();
}
};
47
const createContactSlug = (request, reply) => {
const name = request.payload.name;
const slug = name.split(' ').join('-');
reply(slug.toLowerCase());
};
Route Prerequisites
Methods need to have the same request and reply signature found in handlers
Same access to request information
Calling reply passes control back to the framework
Assigning a value will put it on request.pre in the handler (optional)
48
Route Prerequisites
By default, methods will be called in succession
Putting methods in another array will run them in parallel
BENEFITS
49
Challenge #3
POST SOME DATA
50
Challenge #3
NOTES
51
Challenge #3
BONUS
52
Challenge #3
NOTES
53
4.
Smart Error Objects with Boom
54
When something goes wrong in the request, we need to handle the error
55
ERROR HANDLING
What could go wrong?
56
ERROR HANDLING
Handling Errors
We need to respond with an error code, usually something in the 400s
Provide a message to the user about what went wrong
Doing this manually is a pain
57
Boom simplifies error responses
58
With Boom, just provide a message
59
ERRORS WITH BOOM
const Boom = require('boom');
handler: (request, reply) => {
if (!contacts) {
reply(Boom.notFound('No contacts found!'));
}
}
Call any HTTP error reason
60
ERRORS WITH BOOM
reply(Boom.<error_reason>(message));
Handling Errors with Boom
BENEFITS
61
Challenge #4
RETURN ERRORS WITH BOOM
62
5.
Validation with Joi
63
When it comes to payloads, queries, and parameters, we shouldn’t accept just anything
64
ROUTE VALIDATION
65
<input type="text" placeholder="Name">
<input type="email" placeholder="Email">
Validating Route Data
Validating incoming payloads, params, and queries by hand would be cumbersome
At the end of the day, we’d be writing a library to do this
66
Joi simplifies object schema validation
67
With Joi, describe what the payload, params, or query should look like
68
VALIDATION WITH JOI
const Joi = require('joi');
const paramsValidator = Joi.object({
slug: Joi.string()
});
Be as specific or generic as you want
69
VALIDATION WITH JOI
const payloadValidator = Joi.object({
name: Joi.string().required(),
skills: Joi.string().valid(['javascript', 'node'])
});
Use built-in utilities to ease validation
70
VALIDATION WITH JOI
const payloadValidator = Joi.object({
email: Joi.string().email().required(),
title: Joi.string().regex(/[A-Za-z0-9]/)
});
How do we use it?
71
VALIDATION WITH JOI
config: {
validate: {
payload: payloadValidator
}
}
Object Schema Validation with Joi
What can be validated? Lot’s of things.
72
Object Schema Validation with Joi
Common use cases
73
What happens when validation fails?
74
VALIDATION WITH JOI
error: "Bad Request"
message: "child "name" fails because ["name" is not allowed to be empty]"
statusCode: 400
Challenge #5
ADD VALIDATION FOR OUR ROUTES
75
Challenge #5
NOTES
76
Challenge #5
BONUS
77
6.
Plugins
78
Plugin Ecosytem
Libraries like Boom and Joi are part of the “extended Hapi universe”
Many other (often smaller) libraries outside of the Hapi lineup are provided by the community
Plugins serve a number of needs
79
After installation, plugins need to be registered
80
USING PLUGINS
const Plugin = require('plugin');
server.register(Plugin, () => {});
Most often, we’ll want to register some options
81
USING PLUGINS
server.register({
register: Plugin,
options: {
someOption: true
}
}, err => ... );
Challenge #6
ADD SOME PLUGINS
82
Challenge #6
NOTES
83
WRAPPING UP
84
Hapi.js Offers a Lot
85
Where to go from here
86
Drop me a line
87
@ryanchenkie
chenkie
THANK YOU!
88