Under the hood
The nuts ‘n bolts of Ekko’s architecture
Warning: the post you’re about to read contains a lot of technical talk. If this isn’t your thing, you could probably use this time to enjoy Kurt Cobain’s excellent vocal work on his unplugged performance of “Where Did You Sleep Last Night” from Nirvana’s “MTV Unplugged in New York”. Also, if technical stuff is actually your thing, you may still find the acoustic performance more enjoyable than this post — I’ll leave it to your best judgement.
High-level
Ekko’s split into 3 main systems, all 3 are hosted and run on Heroku. Node.js basically powers every part of Ekko in some way. We use PostgreSQL as our database. The rendering and displaying of Ekko websites, and the theme previews in the Ekko dashboard, are powered by a Node.js library that essentially hands back a React Component that can be ran on the server or client.
Low-level
Henceforth, the 3 apps shall be referred to as “api”, “app” and “serve”. Ekko uses PostgreSQL as it’s main database, with the Node.js library Sequelize providing me with an ORM to interact with it. The only Heroku app that creates and maintains a connection to this database is “api”.
API
As you might imagine, this is the real workhorse of Ekko. It’s a pretty standard Express app that connects to the database, to Stripe, DNSimple, AWS S3, Facebook and Sendgrid. It handles authentication for users signing up and logging in to Dashboard. It receives requests from the “serve” app and returns site data (to be rendered by “serve”). It also provides an endpoint for Facebook to ping (webhook) when an Ekko user’s Facebook Page is updated.
Serve
When you view an Ekko website in your browser, a request will be made to the “serve” Heroku app. It parses the Facebook ID in the URL, or the hostname (ie. http://benhowdle.com) that comes along with the request. It uses these one of these pieces of information to call the Ekko API. It sends a standard POST request and receives an object containing information about the user who owns the requested website, the website data itself, and what Ekko theme is assigned to this website. The “serve” app imports the Ekko-renderer
library and hands it the API-returned data to produce (and serve) a real website.
App
Arguably, the lightest app in Ekko’s arsenal. This Node.js Express app basically serves a client-side React application to power the marketing pages and the Dashboard. The only involvement Express really has it to detect if the visiting user has a cookie set and attempts to call the Ekko API and bring back an active auth session. It then outputs this initial data into the Jade (now Pug?) template for the client-side React app to pick up. All the requests from “app” are AJAX requests straight to the API itself.
Ekko Renderer
This sounds a little more fancy than it needs to. It’s actually up on GitHub, so feel free to have a poke around: https://github.com/Ekko-site/ekko-renderer. The reason for this, is that I list this GitHub repo URL in the package.json dependencies for “serve”. When I push “serve” to Heroku, Heroku will use npm to install Ekko-renderer
from GitHub into "serve" and then launch it.
All of Ekko's themes are contained in this Ekko-renderer
library. This means, if I add a new theme to Ekko, I have to simply push it to GitHub and then re-deploy "serve" to Heroku. The "app" part of Ekko does the exact same thing, except that because "app" is a React app, it just pulls in the latest code locally and Babel builds it directly into the distribution JavaScript code before deploying to Heroku.
This library itself takes in all the API data mentioned above, runs it through a tree of React Components, and returns a top-level React Component. The "app" uses this library to let people preview their Facebook Pages as websites in the dashboard, and try out different themes instantly (because it's a client-side React app), so we just switch out the themeId
prop and the Ekko-renderer
just re-renders. The "serve" app uses this library almost like a custom Express view engine. It functions exactly the same as it does in "app", but this time, it runs it through React's renderToString
method and then uses Express' res.send()
to return the markup to the browser, this means all Ekko sites are universally (isomorphically? What did we all decide on?) rendered. A real plus for SEO and performance.
Ekko would be a little lost without Facebook, because the content from user’s Facebook Pages provide the content for their Ekko websites. When a user signs up to Ekko, they create an account using their email address and by choosing a password, fairly standard stuff. Once they’ve signed up, they then get the chance to connect their Facebook account to Ekko.
I originally had it so that you could sign up just using Facebook, but I wanted the flexibility of people having Ekko accounts, and then subsequently connecting their Facebook account (leaves room for connecting multiple Facebook accounts to their Ekko account further down the line).
I store their Facebook user access token in the database, and use this to fetch their Pages from Facebook API’s /accounts
endpoint. This list of Facebook Pages is then displayed to the user and they get the option to pick one and use it with Ekko.
What happens behind the scenes is that a new record is created in the Pages table in the Ekko database. This record is associated with their user record. One of the columns on this Pages row is called data
. This stores the response from Facebook's API when we fetch their chosen Page, ie. Page name, description, photos, etc...
An Ekko user's website exists with or without the Facebook data. I mean, it will likely display a largely empty page.
When I fetch their Page from Facebook, I call the subscribed_apps
endpoint for that Page and add my own Ekko "Facebook App" to this list. This means that whenever a Page node gets updated (new post, information changes, etc...) the Ekko API gets notified via a POST request from Facebook telling me which Facebook Page got updated. This kicks off a re-fetch of their Page data from Facebook's API. Once this data is validated, the row is updated in the Pages table in Ekko's database, so when the next person visits the user's Ekko site, they see the new content!
Doing it like this means that worst case, either Facebook's API goes down (WE DON'T CRASH, EVER), or there's a token issue, the user's Ekko website still functions...it just displays slightly stale content.
DNSimple
This is the service Ekko uses to handle user’s purchased domains. User’s can search for and purchase domains straight from their Ekko Dashboard. They need to provide a small amount of contact information, which I use as the legal contact information for the new domain name. The alternative, if you already have a domain name, is to simply point a CNAME DNS record at Ekko, which bypasses the need for DNSimple, and the request gets sent straight to Heroku. All the interaction between Ekko’s API and DNSimple is done through their API.
Amazon S3
In Facebook’s API response for a Facebook Page, they return CDN URLs for all the images (ie. profile picture, cover image, photos, post images, etc…). Once I’ve fetched the Facebook Page data from the API, I parse the response, scan for CDN image URLs, download them in-memory, then re-upload them to Ekko’s S3 account. I then save the S3 URL for that image back into the response and store it in the Ekko database. This means any images you see on Ekko websites come from S3, not Facebook’s CDN. The reason for this is that Facebook CDN URLs are prone to changing over time, and I wouldn’t want someone’s Ekko site to suddenly show a bunch of broken images! Unfortunately, the documentation surrounding CDN URL expiry is scarce (read: none), but it’s just something I picked up from various blog posts and Stack Overflow comments.
Redis
Ekko’s “serve” app connects to a Redis server provided by a Heroku add-on. Ekko has a URL format for serving sites, which is https://sites.ekko.site/770309526451072, this number is the Facebook Page’s ID. However, if you append /preview?theme=[a theme ID]
the Ekko API won’t search the database for the page, but rather send a fresh request off to Facebook for the Page’s data, and it will also bypass AWS S3, but simply keep the Facebook CDN URLs in-tact for displaying images.
These preview responses from the Ekko API are stored in Redis for 1 hour. This is ideal for quickly previewing people’s Facebook Pages through Ekko without needing to create a full-blown Ekko account and authenticate a user with Facebook.
Ekko themes
I decided to build Ekko themes using React. It’s what I’m used to using, the rest of Ekko is built using it and I also feel like it’s got a great ecosystem around it, in terms of educational content and community support. It feels like a natural fit, also seeing how much Ekko uses Facebook anyway!
There are two main Components that make up an Ekko theme — the “Default Layout” (provided by Ekko) and the individual theme’s “Layout” Component (provided by the theme author).
The default layout is where I can display things on all Ekko websites, ie. like a notice to say that a user’s free trial has ended and to contact us to upgrade, or during the free trial, there’s a banner along the footer of a user’s site to say “Powered by Ekko” (this disappears for paying users).
For the sake of brevity, this is essentially what the default layout produces:
<head>
<style type="text/css">
<!-- specific theme's CSS -->
<span dangerouslySetInnerHTML={{ __html: this.props.css }}></span>
</style>
</head>
<body>
<div id="root">
<!-- specific theme's HTML (as a React Component) -->
{this.props.children}
</div>
</body>
And a theme will return a valid React Component, something like this:
render() { const { about, cover, picture, name, location, hours,
call_to_actions, events, posts, phone, photos,
description, screennames, emails, id } = this.props.doc.data
return (
<div>
<div className="profile-picture">
<img src={ picture.url } />
</div>
<h4>
{ about }
</h4>
</div>
)
To keep things flexible, and not to restrict theme author’s too much, any fonts needed for the theme, ie. through Google Fonts, are imported in the CSS file using the @import
feature of CSS, at the theme author’s discretion.
If the theme has a need for client-side JavaScript, ie. event handlers once the page has loaded, a separate file called client.js
is created (by the theme author) and detected by the Ekko-renderer
logic and sent to the browser to be executed on page load.
Codeship
I use this service to act as a intermediary between my local development environment and Heroku. I push to the master
branch in each Ekko app which trigger's Codeship running tests on my codebase, and only pushing to Heroku if the tests don't exit (with a failure).
Sentry
I use Sentry to capture exceptions from “app” and “api”.
Papertrail
This is a service which connects to your Heroku app and will ingest logging from your code, making it archivable and searchable for various time periods (based on different plans)
Thanks for reading
Or did you just finish Kurt’s rendition?
I appreciate this won’t be the most universally applicable post, being very Ekko-specific. It’s partially for me to have this all documented somewhere and also to share with people interested in how Ekko’s put together.
If you want to chat about Ekko, or just building products in general, hit me up:
Or more privately, hello@ekko.site.
If you’re interested in giving Ekko a go, head over to https://ekko.site/, or reach us on Twitter:
Or email, hello@ekko.site.
✌️ Ben