Technical Architecture

This document covers Sagefy’s technical architecture. This includes system diagrams, definitions, tools, and high-level decisions. This document does not cover information already in Setup, Technology Stack, User Stories or Cards & Subjects. This document is for new technical contributors and reference.

This document is draft-quality currently. Feel free to suggest edits and improvements!

System diagram

Docker Composeon Ubuntu Serverhttps://sagefy.orgCloudFlareDigitalOceanNginxNode.js ClientExpress, ReactNode.js ServerExpress, PostgraphilePostgreSQLvia dbmateMailgun


Sagefy depends on PostgreSQL. Most of Sagefy’s function is completely in PostgreSQL. We use PostgreSQL thoroughly, including: schemas, enums, comments, composed types, functions, triggers, indexes, foreign key relationships, views, common table expressions, recursion, joins, PLPGSQL, notifications, full-text search, constraints, access policies, and row-based access control.

You can view the PostgreSQL specific files in the postgres/ folder. We use dbmate to maintain database migrations. dbmate automatically generates the latest schema in schema.sql. Sagefy’s database runs on port 2600. We also use the postgres-json-schema extension for some validations.

Tables and relations

A full list of tables and relations is in postgres/schema.sql.


The user table is actually two: one that is public information, and one that is private information. This table is only logged in users.

Subjects are in the subject_version table. subject_version relate to either logged in user or logged out session. Two additional tables, subject_version_before_after and subject_version_parent_child, are join tables. There is also tables entity, entity_version, and subject_entity, which Sagefy uses for maintaining foreign key relationships.

Cards are similarily in the card_version table. A card_entity table maintains foreign key relationships.

The user_subject table stores the intent to learn for users. The table may relate to logged in user or logged out session.

topics are a single table. Topics relate to logged into user or logged out session. Topics belong to subjects and cards.

posts are two tables: post. Posts relate to logged in user or logged out session. Posts may be post, proposal, or vote. The proposal kind joins with card and subject versions via the post_entity_version table.

Running migrations

We use dbmate to run migrations. New migrations are with npm run dbmate new "name", and updates are with npm run dbmate up.

Postgraphile service

What makes the Sagefy PostgreSQL database useful with little custom code is Postgraphile. Postgraphile parses the full PostgreSQl schema and turns it into a GraphQL API. Sagefy follows the advice found in their PostgresQL schema design for PostgreSQL design. This service runs on port 2601. The service reads your .env file, so take care to configure per environment.

We extend Postgraphile and Express here slightly to send emails. We listen to notifications from PostgreSQL, and the service then responds by sending email contents to the Mailgun transactional email service.

You can interact with this service directly on local development at https://localhost:2601/graphiql. The service is self-documenting.

Tests in this directory check both the PostgreSQL schema for correct function as well as the translation to GraphQL.

pm2 manages the Node.js process.

Web client

Sagefy’s web client runs on port 2602. It is a Node.js server with Express. We store a JWT cookie on the client from this service.

The URLs roughly follow a “REST”-like pattern. Most endpoint run a single GraphQL query to the Postgraphile service, format the results slightly, and render HTML with server-side only React.

We have no client-side, in-browser JavaScript. We use plain HTML forms quite often.

There is also minimal CSS, using drab.css and a few small extensions. The CSS can be rebuilt with npm run prepublish, though the need is rare. There are very few classes, preferring simple HTML tags automatically styled.

There’s a folder of GraphQL queries. They are separate because the server/ directory uses the actual client queries to run tests. This avoids duplication.

The top level files in the views/ directory are all pages. A subfolder of components/ are smaller React views shared between multiple pages.

pm2 manages the Node.js process.


Nginx is the only piece that has direct access to the public internet. So we run Nginx on port 80. In addition to routing traffic to the Node.js client service, we also serve some static files directly with Nginx. We take advantage of Nginx’s rate limiting features.

Logging, monitoring, reporting, and alerting

We monitor the up status of the Sagefy with Freshping.


Testing and continuous integration

We run tests on every commit and pull request with CircleCI.

Our test suite runs Jest, Eslint, Supertest, and Joi. Prettier runs with each commit for code formatting.


This is a manual process currently, sshing into the server, cd into the sagefy directory, and running script/