Serverless and WebSockets is a bit of a contradiction. Serverless implies that we’re not managing servers ourselves, they’ve been abstracted away for our convenience. WebSockets allow us to communicate between a ‘client’ and a ‘server’, but what happens when that server is ‘serverless’?! How can the connection be maintained? Are we putting the server back into serverless?
What does serverless mean, actually?
It is often joked that serverless is just “somebody else’s server”. This is the premise of serverless. It is someone else’s server, and also their maintenance, patching and provisioning cycle, operations, capacity planning and engineering.
Serverless providers allow developers to outsource the management of a server, the engineering and cost overhead and only pay for what they use. Serverless allows us to elastically scale with need, precisely because we’re not using the computing power constantly. We pay for the execution that we need and the service ‘spins down’ once the execution is complete. So, the question remains, if we use WebSockets to create a constant connection, will we end up paying for constant server uptime? Is this possible? Is it expensive?
What are WebSockets, actually?
The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user’s browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply. From MDN
WebSockets enable a connection between a browser and a web server which stays open, unlike alternatives like HTTP polling. This persistent connection means that data can be transferred as it happens, in realtime. WebSockets work by establishing a TCP/IP socket connection to a WebSocket server, which remains open for the duration of the session. The client and server then have to decide on a protocol – the sequence and type of messages – to be sent and received over the connection.
Why are people confused by the idea of serverless WebSockets?
If ‘serverless’ is a server maintained by someone else, which spins up and down as needed, and WebSockets are a persistent, open socket connection to a server, then it seems like ‘serverless WebSockets’ is a contradiction in terms. Either the connection is going to be severed or we’ll end up paying for constant compute time, which would imply that maintaining your own server might be preferable. Maintaining your own server of course comes with all of the thought and cost overhead we mentioned before
So when we discuss serverless WebSockets, what we really mean is “a service that will run a server for you, which you can pay for per-consumption, and maintain a persistent connection to, without having to worry about scalability, maintenance, uptime or patching”
Serverless WebSockets with Ably
We are closely watching the rising tide of serverless hosting, and we think it’s both great for the industry and great for users. At Ably, our mission is to make Realtime Edge Messaging simple and seamless - as easy as serverless technology has made app building. Ably allows developers to send millions of messages around the world instantaneously. We do this by provisioning auto-scaling, managed web socket servers that you can use in your apps!
If you’re looking to add realtime, engaging, interactive experiences, to your apps with WebSockets – be they serverless or not – Ably can help out.
Ably does all of the hard work of designing a meaningful protocol on top of the WebSockets connection – providing “channels” and messaging patterns for you to use in your apps rather than just a “naked” WebSockets connection. In the same way that serverless services eliminate the engineering overhead of managing a server, Ably eliminates the engineering overhead of managing a WebSocket connection. Scaling provisions, geographic load balancing, managing lost connections and message ordering is all managed for you.
Now that we understand how to maintain a WebSocket connection on a Serverless Architecture, lets actually try building one. This demo will walk you through using Ably in your Netlify projects.
To use Ably, you will need an Ably API key. If you do not already have an account, you can sign up now for a free Ably account. To create an API key:
- Log into your app dashboard.
- Under “Your apps”, click on “Manage app” for any app you wish to use for this tutorial, or create a new one with the “Create New App” button.
- Click on the “API Keys” tab.
- Copy the secret “API Key” value from your Root key, we will use this to configure your app.
Getting Started with Ably and Netlify
Ably has partnered with Netlify to provide an easy to use GitHub template, which will get you started using Ably in a Netlify powered site.
- Click the ‘Deploy to Netlify’ button above to deploy this GitHub repo.
- When prompted, add the ABLY_API_KEY environment variable with the key you created earlier
Now you will have a Netlify hosted project which uses Ably to maintain a WebSocket connection.
If you already have a Netlify hosted application, it is just as easy to get started with Ably.
Adding Ably to your existing Netlify apps
Ably provides a JavaScript SDK (with TypeScript types), so you only need add a few lines of code to your app to get started:
1. Adding the JavaScript SDK
You will need to use the Ably JavaScript SDK in two places in your application – in the client side application, and in a Netlify serverless function created in the next step. (Netlify supports many different frontend frameworks and tools, so you may need to adjust the following for your stack). To install the Ably-JS package as a dependency run:
npm install ably --save
- Add a Netlify Serverless Function to manage the Ably API key
In order to keep your Ably API key secret, to protect it from misuse, you will need to add a Netlify function to support Token Authentication. This function will be called by the client side Ably SDK instance to get a temporary, short lived token, that will be returned to the browser and used to authenticate the connection with Ably.
First, create a new environment variable within Netlify called ABLY_API_KEY
and give it your new Ably API key as a value.
Next, create a new function to read that variable and call the Ably SDK. By default, Netlify expects functions to be found in
/.netlify/functions
It is possible to override this default location if necessary, so adjust as appropriate for your existing solution.
Create a new directory for the function, called ably-token-request.
Inside this directory, create two new files:
ably-token-request
├── package.json
└── index.ts
The package.json file looks like this:
{
"name": "ably-token-request",
"version": "1.0.0",
"description": "ably token request",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Netlify",
"Serverless",
"Typescript"
],
"author": "Netlify",
"license": "MIT",
"dependencies": {
"@netlify/functions": "^1.0.0",
"@types/node": "^14.0.0",
"typescript": "^4.7.4",
"ably": "^1.2.29",
"dotenv": "^16.0.1"
}
}
This is a basic NPM package definition with a few dependencies - the @netlify/functions
package for Type definitions, @types/node
for Node types, TypeScript, the Ably SDK, and dotenv
which will load environment variables during development.
Now, update the index.ts
file with the Netlify function code:
import * as dotenv from "dotenv";
import * as Ably from "ably/promises";
import { HandlerEvent, HandlerContext } from "@netlify/functions";
dotenv.config();
export async function handler(event: HandlerEvent, context: HandlerContext) {
if (!process.env.ABLY_API_KEY) {
return {
statusCode: 500,
headers: { "content-type": "application/json" },
body: JSON.stringify(`Missing ABLY_API_KEY environment variable. If you're running locally, please ensure you have a ./.env file with a value for ABLY_API_KEY=your-key. If you're running in Netlify, make sure you've configured env variable ABLY_API_KEY.`)
}
}
const clientId = event.queryStringParameters["clientId"] || process.env.DEFAULT_CLIENT_ID || "NO_CLIENT_ID";
const client = new Ably.Rest(process.env.ABLY_API_KEY);
const tokenRequestData = await client.auth.createTokenRequest({ clientId: clientId });
return {
statusCode: 200,
headers: { "content-type": "application/json" },
body: JSON.stringify(tokenRequestData)
};
}
This function, when called, loads your API key from Netlify environment variables and uses the Ably SDK to create a temporary token for your application to use. The client-side SDK takes care of refreshing this token during long-lived sessions.
3. Adding Client Side Code that uses Ably
Depending on how your application is built, your usage of Ably will be different. Below is a vanilla TypeScript example that uses Ably to create a new authenticated instance of the SDK, and subsequently subscribe to, and publish a message:
import { Types } from "ably";
import * as Ably from "ably/promises";
(async () => {
const optionalClientId = "optionalClientId";
// When not provided in authUrl, a default will be used.
const connection = new Ably.Realtime.Promise({ authUrl: `/.netlify/functions
//ably-token-request?clientId=${optionalClientId}` });
const channel = connection.channels.get("some-channel-name");
await channel.subscribe((msg: Types.Message) => {
console.log("Ably message received", msg);
document.getElementById("response").innerHTML += "<br />" + JSON.stringify(msg);
});
channel.publish("hello-world-message", { message: "Hello world!" });
})();
It’s worth noting that when instantiating an instance of Ably.Realtime.Promise
(the Ably SDK), we’re providing an AuthUrl parameter that points to the Netlify function we created in the previous step. This is all that you need to do to make sure you’re securely authenticating with Ably.
Serverless WebSocket to the World
It is our hope that this template and walkthrough will have demystified the premise of Serverless WebSockets. You are now empowered to create all of the interactive, multi user, engaging experience that WebSockets allow in your serverless environments without any of the operational overhead! Go forth and message!