Lately, I have been enjoying three great new tools: Zeit's now for clean fresh deploys at the speed of light, Cypress.io for quick and painless end to end browser testing and Feathers for my JavaScript real time server. In this blog post I will explain how to work around the existing limitations in Zeit deploys - mainly your data is temporary and not shared among deploys. Unlike Dokku each Zeit deploy only can write to a local temporary location, thus there is a problem where to store the data and how to roll it over to the new server.
Of course, you can use a data backend, like Firebase. But keeping the data local is usually faster and simpler for quick prototypes. I prefer to keep the database small and just copy it from the previous deploy to the new one.
Example application
Take Feathers chat app for example. It has two services messages
and users
. Both services use NeDB to store the data in a local
file. NeDB is fast, simple to use and mimics MongoDB API for document storage
and retrieval. For quick prototypes or simple projects it just works.
The data for each service is stored in a plain text file;
the local user login and password database looks like this
1 | $ cat users.db |
Notice that passwork is hashed and salted for security. Even if an attacker steals this database, it would take a long time to match a password to the value stored inside the DB. Consult Feathers security docs for details.
My copy of the chat application is at bahmutov/feathers-chat-app and has additional middleware feathers-nedb-dump I wrote to return a given service's database file. It can also replace the running service's database with uploaded value. Taken together this middleware allows to roll over data each time I deploy new "production" app to Zeit.
Middleware
The feathers-nedb-dump is simple to use. Just configure the secret token used to authorize the API call to return or receive the database, and add two routes.
1 | { |
1 | // src/middleware/index.js |
You can use other API endpoints for obscurity, but more importantly, you have to use HTTPS to prevent token from leaking. Zeit always uses HTTPS, so this should be safe.
Deployment steps
Zeit environment restricts the applications to only write to /tmp
folder.
We can of course configure our Feathers chat application to do so
1 | { |
Imagine we have an existing application running at
https://feathers-chat-app-one.now.sh
placed there some time ago
using the Zeit now
command. The deployment has new users, and has chat
messages. Now we have (hopefully) fixed a discovered bug and want to deploy
new application version. Rather than overwriting the code at the existing
url, we are going to deploy to a brand new environment. Let us say
that now
has given us a fresh server at a different url
https://feathers-chat-app-two.now.sh
. We want to copy data from -one.now.sh
to -two.now.sh
server. Just use the following script, which copies
the two databases. I am using httpie instead of curl
for
a more user-friendly API; you can find this script in
copy-data.sh file.
1 | FROM_HOST=https://feathers-chat-app-one.now.sh |
After a few seconds the new server has a replica of the dataset from the previous server (which is still running!)
End to end testing new deployment
The web application is now running at two locations. Let us test the new deployment. The end to end testing has never been easier with Cypress. For example here is a test that logs into an existing account (possible because we rolled the data) and sends a test message.
1 | describe('Chat app', function(){ |
In Cypress we can observe the test execution, and each step provides a lot
of information. For example the last assert verifies that the test message
we just sent is actually present in the message list:
cy.contains('.message', msg).should('exist')
. When hovering over this
assertion in the test runner column, we see the found element highligted
in the iframe on the right.
By default, Cypress runs against the server base url specified in the
cypress.json
file. When testing new deployment we can specify a new
url using either command line or environment variable.
1 | $ CYPRESS_baseUrl=https://feathers-chat-app-two.now.sh cypress open |
This makes it extremely easy to verify that the new deployment is functioning. After the test passes, we can copy the data again - this makes sure the new deployment has the very recent data snapshot, and removes any traces left by the end to end tests.
Switching on the new deploy
Once we have tested the new deploy, we can use zeit alias to point the public URL at the new deployment. The users should not notice any changes, since the data from the previous deployment has been copied, and the environment has been tested.