How to Build a Serverless Blog on CodePen

January 15, 2018

Serverless.

It's kind of a phenomenon. All sorts of web developers can make use of it, including front-end developers! Here's one way to think about it: they allow you to take your front-end skills and do things that typically only a back-end can do. Depending on what you all make use of, serverless is possibly more accurately referred to as Cloud Functions or Functions as a Service.

Here's a rather remarkable thing serverless can make possible on the client: saving and reading things from a database. That's right! It's possible to have a database and deal with it entirely through front-end code. Technically, the database still exists on a server, so the word serverless can feel a little disingenuous, but it's not a server that you need to buy and build and maintain and deal with directly.

Why bother with doing a database this way? For one thing, it means you can host the rest of your site much more easily. Wanna build a React-powered blog on GitHub pages? You totally can! Wanna use the super fast static file host Netlify to build a Vue-based community-driven recipe site? Do it.

I bet where you can see this is going: serverless opens up tons of possibilities for what you can build right here on CodePen! Let's dig into this and build ourselves a fully functional blog.

That's right, even though CodePen itself doesn't have data storage, you could use serverless technology to handle all that.

Tools of the Trade

There are lots of possible tools in the world of serverless, but for this demo, in addition to building the front end on CodePen, we'll be doing the serverless stuff on Firebase, so you'll need a Google account.

Step 1) Set up a Firebase project

Let's create a project and then import some placeholder data (literally: some fake blog posts) that we can use initially as we develop our API.

Create the project by providing a project name. The project ID is provided for you, but you can edit it, if you'd like. The name is just for finding your project again in your list of projects, but the ID is used in your actual code.

We're just working on the web here, but note that your Firebase projects can be used on both iOS and Android apps as well (like, simultaneously).

Let's consider the data for a blog post.

We have to put our content hats on for a minute and consider the bits of data that a blog post might have. Let's keep it real simple and go with title , content , posted date , and author. I'm sure you can imagine a more elaborate data structure, but this'll do for now. You aren't locked to this, it can always be changed later.

Given these, we can structure our first implementation data like this, with the created field being a Unix timestamp:

If you have an existing project you're reusing, this action will overwrite any data you currently have stored in your project, making a new project recommended for this tutorial.

Select "Import" to save the initial data.

We now have our Firebase project created, development data in our data storage, and are ready to start developing in earnest!

Step 2) Set Up Cloud Functions

First, let's make something clear: You don't need to work with Cloud Functions use Firebase realtime databases. We could make this project entirely through client-side JavaScript. We went the route of Cloud Functions because they are powerful and a big part of understanding the possibilities of **serverless*. Now let's get into what they are.

Firebase Cloud Functions are (nominally) small pieces of code that run on discrete data. In our case, we want to start with a function that will provide a list of all posts.

While the setup is slightly different for Cloud Functions than for many front-end libraries, developers comfortable with JavaScript on the front-end will feel at home with the Firebase Cloud Functions.

On the command line, install the firebase tools globally:

npm install -g firebase-tools

Once installed, create and/or navigate to your project directory. This is where the back-end, serverless code resides locally. We'll develop, test, and deploy from this project directory.

In the project directory, run:

firebase login

The login command will provide a URL and open your default browser, where you can log in to your google account, and grant access to the firebase command line tools.

Of note, I find that the authentication process happens entirely in the browser particularly awesome. That the process can be done from other devices if you're on a headless server, say, is even more spectacular. You can, for example, confirm the tools in your phone's browser. Totally awesome.

With a successful login, we can develop our project locally and deploy our tested Cloud Functions to Firebase.

In the project directory, run:

firebase init functions

Firebase has a number of features, functions is only one of them. You can also have hosting, file storage, and event triggers. We'll use only functions initially.

? Select a default Firebase project for this directory:
[don't setup a default project]
❯ Duncan (duncan-131)
Firebase Demo Project (fir-demo-project)
[create a new project]
=== Functions Setup
A `functions` directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.
? What language would you like to use to write Cloud Functions? JavaScript
&#x2714; Wrote functions/package.json
&#x2714; Wrote functions/index.js?
Do you want to install dependencies with npm now? (Y/n)

Allow firebase to install the dependencies by saying yes, to the dependencies install prompt:

We are defining the cloud function posts. In it, we are using the Firebase real-time database (essentially a JSON object) for all the values below the /posts reference, which is at the top (or root) of our JSON.

Accessing values in the JSON is via the path into the JSON.

once() is a database action that triggers data retrieval one time. In our code, we want the value event. The value event is sent every time data is changed at or below the reference specified in the ref() call. Because every data change will trigger the value event, use it sparingly.

When the database reading is done and we have the once() call complete, we can send the snapshot of our data back on the request.

Now that we have code for our first API endpoint, we want to test it. We never, ever want to deploy untested code.

To test locally before deploying, the firebase command has a serve function that creates a local server which uses the remote datastore - we can access our database locally.

First run:

firebase use --add

And select your project you've already started. Then to start the local server, from your project directory, run:

firebase serve --only functions

This will start the local server and print a URL like:

http://localhost:5000/PROJECT_NAME/PROJECT_REGION/posts

If the command doesn't open a new browser window, copy the localhost URL into your browser.

Step 4) Setting up the Front End

This is where CodePen comes in! We need someone where to build our front end. If you're just playing around, creating a Pen will do. If you're looking to really build this thing out, you'll probably want to create a Project (so ultimately you can break things up into multiple files, upload other resources, deploy it, etc.).

Let's start with some clean HTML

We'll ultimately be asking for posts data from our API, and formatting it into HTML. It's usually a smart idea to start with good HTML first. Here's an example of that, including a div with an id we can easily target with JavaScript to insert our articles ourselves later.

When testing, /posts/ should return a JSON list of all of our test posts. The route /posts/101 should return the JSON for the single post with ID 101. The route /posts/aaa should return an empty JSON object, an error state we'll fix shortly.

What we might notice in testing is that while /posts/ returns our full JSON posts list, /posts returns an error Cannot GET null. This is because the router is receiving an empty URL path. This is an odd quirk of the router that we can work around by replacing our onRequest call with a check for the missing trailing slash:

Now, when we click on the "Read More" links from our posts list, the individual blog entry displays.

We can quickly see where we can add UX enhancements such as adding a spinner when we select "Read More" to indicate to the viewer a post is loading, or a link to return to the list of posts. Creating new posts, however, is more exciting, so let's do that first.

Step 6) Creating New Blog Posts

What we've done so far is fetch existing posts and display that. That's great, but if we're really building a blog, we need a way to create new blog posts as well.

Our current process for creating a new post would be to upload a JSON directly through the Firebase interface to add a new post. Possible, but that's no way to live. Let's allow ourselves to create a new blog post right from the site itself!

Keeping in line with API best practices, we want to add a POST action to our /posts API endpoint. The fields we have from our GET endpoint are: author, content, created, and title. The server should handle the created time field, so let's focus on the content and title fields.

To the /posts API endpoint with the POST method, we'll send JSON formatted like this:

{
"title": "The Post Title",
"content": "This is our example content for this post. It is currently unformatted. Formatting is important, though!"
}

To handle this incoming data, let's use the Express .post routing method, adding the new code just before the exports.posts call:

Of note, we use the set() method here for the creation of the post. The set() method will take all the incoming keys and save them and their data, and only them, removing any other JSON key values in the object. The update() method will save the new values of only the keys provided, making update() great for edits, and set() good for creation.

After adding the post function, we again test locally with firebase serve --only functions . To POST to the endpoint, we can use curl:

If the CURL POST call works, we will see the additional post item on the posts list when we refresh the pen.

When everything is working, do the ol' firebase deploy --only functions to deploy.

With these changes, we've done several things with our data:

we've ignored the author field

we've just opened up our application to a huge security hole.

The former is part of the development iteration process, which test cases will help find. The last is a huge concern. We don't check or clean the incoming data for hacks, we don't check or clean the data we send back out, and we currently allow anyone to post to the URL, as we don't have any authentication on the POST endpoint.

Let's fix that last one by adding authentication. The first thing we want to do is lock down the API by limiting access. Navigate to the project's Firebase console:

Selecting the Add Firebase to your web app will trigger a popup, with code that will handle authentication in the blog app.

Copy these values, and paste them at the bottom of the HTML in the Pen.

Next, we'll select the sign-in methods allowed for the blog. In the Firebase console, under the Development tab on the left, select the Authentication menu.

Select the big Set Up Sign-In Method button for a list of providers.

In the list, select the providers you'd like to support. Firebase provides a fantastic list of providers.

Select at least one.

Next, add "codepen.io" to the list of authorized domains on the sign-in method page. If you're using Projects, add "codepen.plumbing" and "codepen.website" as well.

Authentication tokens from the client can be sent in the request headers or in the request body, depending on how we send them from the blog front-end. Let's send them in the request body's JSON to reduce complexity, using the token key.

With this, we can use request.body.token value to read the request's authentication token we're going to pass to the server, and Firebase's verifyIdToken() function to confirm the user is valid:

Using the decoded user fields, we can extract the user's name for storing in our post's JSON. This value is good for display, but not unique. As such, we'll use it in this iteration, but refactor to use the unique ID Firebase provides later.

The last bit we need to add is the ability to submit blog posts from our front-end. We previously added the Firebase authentication code to our HTML. Let's add a "Sign In" button to trigger the Firebase authentication process, and a "New Post" button, which we can display after sign-in:

After adding the JavaScript, give the log in a try. The "New Post" button should display after authentication, and only if a user is authenticated.

We can't rely on the button's display state for authentication status - a simple HTML change in the pen can display the "New Post" button. However, because we check authentication status in the POST API method, we are okay if this HTML change happens, as it won't introduce any security issues.

Now that we have our "New Post" button displaying after authentication, add the new post form:

Step 7) Handle URL Changes (and cleaning up)

We now have a list of all of our posts, we can view single posts, and we can create new posts. The site has the authentication "feature" that anyone with a Google account can create a new post, as well as the limitation that we are unable to link to a specific post.

We also know we can make the post rendering easier by creating a template, so let's start there, before tackling URL handling and authentication limits.

First up, create a render function that can render both a full post, as well as an excerpt:

Next, let's handle the URL change states - we want the browser URL to reflect the blog's content, either a specific post or the list of posts, that we load via the Firebase AJAX call. This is the perfect use for the HTML5 History API, updating the URL on content changes, and using the updated URL to load individual posts.

Lastly, we want to limit who can write data to our blog. Currently, the demo permissions are open: anyone authenticated with a Google account can read and write posts. We can change these access rules in the Firebase Console.

A user's UID (abcdef is an invalid UID) can be found in the Authentication section of the Develop menu, in the Users tab in the main window:

This limits who can create a post to only a single authenticated account, which wraps up this serverless tutorial. We can view all our posts, view a single post, submit a new post, use the browser history, and authenticate users, all without spinning up a single server.

Keep on going!

While we now have the basics of a blog hosted on CodePen using Firebase, all serverless, in a production scenario, we would set up development, staging, and production versions, then keep iterating: improving the design and adding features. Here are some ideas:

Loading icons indicating API calls are happening

A Markdown editor for styling

Comments on posts from authenticated guest users

Voting or favoriting posts

Editing of posts by implementing the PUT action in our API

Communal blogs with multiple authors, including filtering by author

Refactor the author field with unique IDs provided by Firebase authentication, along with their displayed name

Pagination

Use something like Angular, Vue, or React

Again, we do all of this without having to spin up a traditional server. Awesome!