Bringing spatial interfaces to Zeit’s Now v2 platform
I have previously written about GEOLYTIX’ work to make spatial interfaces serverless with Zeit’s Now platform. Soon after I published that article, Zeit introduced the second coming of their Now cloud platform for static sites and Serverless Functions. Everything changed and we had to go with the flow.
Now Again with Serverless Function
Being able to deploy functions written for the Node.js runtime allowed us to break up the XYZ web server into individual handler for each endpoint. Once build, each handler will be a Serverless Function in an immutable deployment hosted as a Zeit Project. Zeit uses regional AWS cloud services as origin for Serverless Functions as well as the Google Cloud Platform to provide edge caching for static content in the Zeit CDN.
Benefits
Abstracting away cloud providers improves resilience with a minimum off fuss by not having to deal with different cloud consoles. Having no servers to maintain while further reducing NoOps costs makes the Zeit Now platform the most reasonable choice for GEOLYTIX’ cloud services. As of March ’20, Zeit even offer a free tier for hobbyists.
Reducing cost is at the heart of going serverless. Whether it is the price of Metal, DevOps or redundant uptime.
From serverful to serverless function handler
The legacy serverful behavior was to deploy a code repository to Zeit and let the Node.js runtime build and subsequently run a Fastify web server. Serverful deployments were scaled to have a minimum 1 instance running at all times due to the associated startup time of 10+ seconds. We barely ever needed to scale up services with most instances only being frequently used by individual users. A vast majority of requests being directed at only a handful of endpoints providing data for map view change events. Breaking up the web server into individual function handlers for each endpoint was a logical step towards a pay-per-use interface for spatial data.
With all transformations and calculations being handled by the database backend our primary performance concern was the memory footprint of each handler and the ability to quickly scale functions for frequent synchronous requests.
There is an excellent article by Brooks Mikkelsen which lists the benefits of a serverless REST API and how to design a HTTP server for Zeit Now.
👉 Designing a Serverless Express.js API using Zeit Now
Node.js HTTP server
The now/node builder provides the IncomingMessage and ServerResponse from the node HTTP server as parameter types to the module export function from each endpoint handler.
Middleware / Endpoint authentication
The Zeit Now CLI and now/node builder are still in beta and some capabilities are yet to be documented and tested in anger. At the time of moving our project from v1 to v2 it was not entirely clear how to use Express middleware with the now/node builder. In order to secure the REST API we built our own authentication logic to intersect all incoming requests. Being stateless, each private request must bear a signed token to be validated by a node implementation of jsonwebtoken.
The root handler for our API should return an application view template (html) from the API.
Outside the async module exports function we define the authentication module. The auth module will validate a signed IncomingMessage (req) token and finish the ServerResponse (res) by sending a 4** status code to the client. This would occur if a token has expired. Public endpoints do not require a token. The auth module will respond with a login view if a token is required to access private endpoints.
Configuration Caching and RDS Connection Pooling
In order to cache configuration objects and templates we define a const object and an async method to retrieve the templates object outside the handler.
The async module function to retrieve templates will await for all templates to be loaded from local scripts, database or 3rd party service providers before returning the result to the endpoint. A module should resolve immediately with resources such as the API workspace being cached outside the exported module method.
The endpoint will await for the templates to be retrieved and assign the returned templates object to the const defined outside the handler.
Modules may require database connection methods. We use node postgres connection pooling for queries to connected databases. The connection pools are also cached outside the module handler.
The following endpoint will cache database connection pools dbs and a layers configuration object outside the handler.
The layer will be queried from a PostGIS table if a request passes authentication. Data will then be sent as geoJSON to the client.
Routing with now.json rewrites, builds, and headers
The now.json configuration defines build scripts for endpoints, HTTP request/response headers, and route rewrites for deployments to the Zeit now platform.
Environment variables defined in the now.json allow node postgres to create connection pools.
Builds define a path directory routing structure where the root.js module script in the /api directory will be accessible via host:3000/api/root
Routing can be further defined by using rewrites.
Headers allow us provide additional security headers for all API endpoints.
Debugging in VScode with express.js
Using now dev allows to locally build and serve the API. Logs and Error messages will be sent to the terminal console. Debugging methods with breakpoints and variable inspection in VScode is currently not possible with now dev.
node http server methods used by the now/node builder are fairly similar to the same server methods in express.js.
We can create an express server script to host the same endpoints. If launched in a VScode debug session we can use all the code debug tools.
Deploying
The API may be deployed to the cloud by providing the now.json to the Zeit now CLI.
👏 👏 👏
I hope this article will be helpful for you if you are building a serverless REST API. This may also provide a better understanding of the Open Source XYZ framework. It certainly will help myself to get feedback which I can feed back into the XYZ documentation.
Big thanks to @paulogdm and the @zeithq team for all the help along the way. It must have taken us a dozen or more Spectrum posts to get here.