Dan Zajdband

Nowhere to SSH: my journey into the Serverless world

tl;dr: I migrated a small real-world app from Express + MongoDB to Now + Firebase. I'm happy I was able to do it in ~10 hours and I don't need to maintain or spend money on servers (at least for now).

Since I'm terrible at managing my personal projects servers I wanted to believe the hype around Serverless architectures. My intention was to go to a scenario where I don't have to worry about scalability, OS vulnerabilities, web servers, SSL certificates or database backups. Time will tell but for now it seems that's the case.

The state where I was in

To start experimenting with this technology I chose GuriVR. It was a Preact app with a tiny Express backend using MongoDB as the primary database, you can explore the old codebase. It was hosted on a $10/month DigitalOcean droplet along with other apps and sites.

Changing the mindset

I looked at different platforms and frameworks and decided to give a chance to Now from Zeit, the magic now command played an important part in the decision.

What I understood from the docs is that in the serverless world you get a kind of clean slate on every request. The goal is to return as fast as possible and avoid persistent connection. Firebase was a great fit for my need because it offers two products I needed to implement: Stateless authentication instead of the MongoDB backed user sessions in the old system and the Firestore database. One functionality I wasn’t giving up easily that I got for free in Firebase was the “passwordless” authentication.

Besides the database and authentication provider change I haven’t had to change too much. The Frontend compiles to a static app that Now delivers super fast with their Smart CDN. The Express app was easy to convert. I just moved each endpoint to a separate file, matching the API structure. For the frontend I used @now/static and for the backend @now/node.

Most of the ~10 hours I had to work on the migration where spent implementing the new firebase authentication and updating the database calls to use Firestore. I also moved the file uploads from AWS S3 to the Firestore Storage service just because I thought it was better to have all the services in the same provider and dashboard.

Platform specific configuration

Besides moving the backend endpoints to different files exporting the lambda functions I had to create a now.json file to hep Now figure out how my project should be deployed. This was the first time I did this and I’m sure there are better ways to do it but this is what I got:

  "version": 2,
  "name": "gurivr",
  "builds": [
    { "src": "api/**", "use": "@now/node" },
    { "src": "client/dist/**", "use": "@now/static" }
  "routes": [
    { "src": "/api/preview", "dest": "api/preview.js" },
    { "src": "/api/stories", "dest": "api/stories.js" },
    { "src": "/api/stories/([^/]+)", "dest": "api/story.js?id=$1" },
    { "src": "/api/assets/search", "dest": "api/assets/search.js", "headers": { "Access-Control-Allow-Origin": "*" } },
    { "src": "/(login|guide|stories)", "dest": "/client/dist/index.html" },
    { "src": "/stories/create", "dest": "/client/dist/index.html" },
    { "src": "/(.*)", "dest": "/client/dist/$1" }
  "env": {
    "FIREBASE_ADMIN": "@firebase-admin",
    "FLICKR_SECRET_KEY": "@flickr-secret-key",
    "GMAPS_SECRET_KEY": "@gmaps-secret-key",
    "FREESOUND_SECRET_KEY": "@freesound-secret-key",
    "STORAGE_BUCKET": "@storage-bucket"

This was maybe the trickiest part of the project but it wasn’t that hard to be honest. The development phase was a delight. To start a local server I just typed now dev and that was it. The node.js backend and the frontend were up in about one second. It also offers live-reload, something that I wasn’t even expecting 😍. On development mode you can set a .env file to provide environment variables and secrets.

Every time I wanted to see if everything worked well “in the internets” I just used the now command that returns a nice development url for every deploy.

The “deployment”

This felt like cheating. For deploying to production I had to do 2 things. The first one was setting up the secrets using the now tool. This means typing now secrets add my-secret ’42’. Then, to get a production build I executed again the now command but with the –prod flag, and that was it (I promise).

What’s next?

You can see the diff on the codebase and get your own conclusion. My next move will be to figure out how to move this blog and my other projects to this schema. You can reach me on twitter if you have questions or want to add something to the discussion.