{"id":13622645,"url":"https://github.com/hasura/3factor-example","last_synced_at":"2025-04-05T21:09:32.817Z","repository":{"id":39543645,"uuid":"159175151","full_name":"hasura/3factor-example","owner":"hasura","description":"Canonical example of building a 3factor app : a food ordering application","archived":false,"fork":false,"pushed_at":"2023-07-11T19:32:58.000Z","size":1923,"stargazers_count":459,"open_issues_count":56,"forks_count":43,"subscribers_count":28,"default_branch":"master","last_synced_at":"2025-03-29T20:06:58.856Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://3factor.app","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hasura.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-26T13:37:01.000Z","updated_at":"2024-11-19T04:03:44.000Z","dependencies_parsed_at":"2024-08-01T21:54:33.696Z","dependency_job_id":null,"html_url":"https://github.com/hasura/3factor-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasura%2F3factor-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasura%2F3factor-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasura%2F3factor-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasura%2F3factor-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hasura","download_url":"https://codeload.github.com/hasura/3factor-example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247399878,"owners_count":20932880,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T21:01:22.196Z","updated_at":"2025-04-05T21:09:32.786Z","avatar_url":"https://github.com/hasura.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# 3Factor Canonical App\n\nThis is a canonical app for 3factor which showcases the three factors in detail with a reference implementation. The reference implementation is a food ordering app which has a user facing component (order-app) and an admin dashboard (analytics-app).\n\nYou can follow along this step-by-step guide to deploy and evaluate this application design pattern yourself.\n\n## What is a 3Factor App?\n\nFrom [3factor.app](https://3factor.app/) :\n\n\u003e Today, it is possible to build backends for apps that allow for fast iteration,  while being resilient and highly scalable from the get go.\n\u003e\n\u003e We propose an architecture pattern which is composed of 3 factors:\n\u003e\n\u003e 1) Realtime GraphQL\n\u003e\n\u003e 2) Reliable eventing\n\u003e\n\u003e 3) Async serverless\n\nHere is the high-level diagram comparing a traditional architecture vs a 3factor architecture:\n\n![3factor-migration](https://3factor.app/3factor-migration.png)\n\n## Step-by-Step Guide\n\n### Stack\n\nNodeJS 8.1\n\nPostgres 9.5+\n\nHasura GraphQL Engine\n\nAWS Lambda\n\n### Step 1: Model application\n\nThe core modeling of a 3factor app is very similar to traditional application modeling: we start by defining the schema and then define different functional components. The main difference is that 3factor emphasizes that each function be atomic (i.e. either happens completely or doesn't happen at all) and invoked via (persisted) events.\n\n#### Schema\n\nThe schema of our application is defined in [schema.sql](schema.sql) which you can apply on your postgres database.\n\n```bash\n\n$ export POSTGRES_CONNECTION_STRING='postgres://postgres:password@localhost:5432/postgres'\n\n$ psql $POSTGRES_CONNECTION_STRING \u003c schema.sql\n\n```\nIn the above snippet, we are running a postgres database on localhost.\n\n#### Functions\n\nNext, let's design the order workflow and describe its functional components. The order workflow consists of all the steps from a user placing an order to delivery agent assignment for the order.\n\n**Order workflow:** Login -\u003e Place order -\u003e Validate order -\u003e Payment -\u003e Restaurant approval -\u003e Agent assignment\n\nLet's describe these steps in detail:\n\n1) **Login:** A user enters the app using a username. For this demo, there is no authentication.\n\n2) **Place order:** The user selects food items and places an order.\n\n3) **Validate order:** As soon as an order is placed, it is validated in the backend.\n\n4) **Payment:** After the order is validated, user is requested for payment.\n\n5) **Restaurant approval:** After successful payment for the order, restaurant receives and approves the order.\n\n6) **Agent assignment:** After restaurant approves, an agent is assigned for delivery of the order.\n\nNext, let's get into development.\n\n### Step 2: Setup a realtime GraphQL interface\n\n3factor requires the frontend use GraphQL for querying and performing actions. The reason for this is two-fold: fast iteration and realtime feedback. \n\nWe will use Hasura to get GraphQL APIs over our existing postgres database. We will use Docker to run Hasura. If you do not have Docker, you can install it from [here](https://store.docker.com/search?type=edition\u0026offering=community).\n\nIf you have a remote postgres database, run the following command:\n```bash\n$ docker run -d -p 8080:8080 \\\n  -e HASURA_GRAPHQL_DATABASE_URL=$POSTGRES_CONNECTION_STRING \\\n  -e HASURA_GRAPHQL_ENABLE_CONSOLE=true \\\n  hasura/graphql-engine:latest\n```\n\nIf your postgres database is running on localhost, run the following command instead:\n\n```bash\n$ docker run -d --net=host \\\n  -e HASURA_GRAPHQL_DATABASE_URL=$POSTGRES_CONNECTION_STRING \\\n  -e HASURA_GRAPHQL_ENABLE_CONSOLE=true \\\n  hasura/graphql-engine:latest\n```\n\nOpen the Hasura console by visiting http://localhost:8080/console. In the `Data` tab, you will see all the tables in our postgres database. Just track them all to get GraphQL APIs over them instantly:\n\n![track-all](assets/track-all.png)\n\nHasura will also detect relationships (via foreign-keys) automatically and you can track them as well to get GraphQL APIs over relationships:\n\n![track-all-relations](assets/track-all-relations.png)\n\n### Step 3: Local development \n\nNow, we can write our frontend using GraphQL APIs. We can perform the following actions directly via the frontend using authenticated GraphQL APIs:\n\n1) **Login**\n2) **Place order**\n\nRefer to [src/order-app-frontend](src/order-app-frontend) for the frontend source code. Run the frontend app as follows:\n\n```bash\n$ cd src/order-app-frontend\n$ npm install\n$ npm start\n```\n\nWe need to setup a development environment for our backend. We need to write backend logic for the following steps:\n\n1) **Validate order:** Source code: [validate-order](src/backend/validate-order)\n\n2) **Payment:**  Source code: [payment](src/backend/payment)\n\n3) **Restaurant approval:**  Source code: [restaurant-approval](src/backend/restaurant-approval)\n\n4) **Agent assignment:** Source code: [agent-assignment](src/backend/agent-assignment)\n\nFor this purpose, we will run a node server with each of the above functions exposed as HTTP APIs as defined in [src/backend/localDevelopment.js](src/backend/localDevelopment.js). Run the server and try these functions out:\n\n```bash\n$ cd src/backend\n$ npm install\n$ node localDevelopment.js\n\nOutput: server running on port 8081\n```\n\nIn a different terminal: \n\n```bash\n$ curl -d '{\"order_id\": \"abc-ad21-adf\"}' -H 'Content-Type: application/json' localhost:8081/validate-order\n```\n\n### Step 4: Setup event system\n\nNow that we have our frontend components and backend components ready, it is time to glue everything together via events. The event system is at the center of 3factor architecture. The event system is what drives the entire workflow: from the frontend initiating the events to the backend triggering functions on emitted events.\n\nThe order workflow is initiated by the user creating an event (via an insert to the order table) and ends with the backend creating an event for agent assignment (via an update to the order table). \n\nIn the frontend, we will subscribe to the events on `order` table via realtime GraphQL and update the UI.\n\nIn the backend, we will use Hasura Event Triggers to invoke webhooks when events are emitted. The backend requires the following Event Triggers:\n\n1) validate-order: On `insert` of an order.\n2) restaurant-approval: On `update` of an order after successful payment.\n3) agent-assignment: On `update` of an order after restaurant approval.\n\nLet's setup these triggers with our locally deployed functions: [localDevelopment.js](src/backend/localDevelopment.js). We can do this either interactively via the Hasura console or through Hasura API. Run the following command to setup these event triggers via Hasura API:\n\n```bash\n$ curl -d @event-triggers.json -H 'Content-Type: application/json' localhost:8080/v1/query\n```\n\nGo back to the Hasura console at http://localhost:8080/console and in the `Events` tab you will see the newly created Event Triggers:\n\n![event-triggers](assets/event-triggers.png)\n\nThis finishes the entire development cycle on our local machine. You can start testing the app now.\n\n### Step 5: Use serverless functions\n\nNow, that you have locally developed and tested your app. Let's deploy all these functions to AWS Lambda and update the Event Triggers from localhost HTTP APIs to Lambda APIs.\n\nServerless functions are a crucial component of 3factor as it provides infinite scale, no-ops and optimal cost.\n\nTo prepare our HTTP APIs for Lambda, we need to wrap the business logic in a Lambda \"context\". The Lambda context for `validate-order` is given in [validate-order/lambdaCtx.js](src/backend/validate-order/lambdaCtx.js). Let's package this as a zip file and deploy to Lambda:\n\n```bash\n$ zip -r validate-order.zip validate-order/*\n```\nDo the same for the other Event Triggers.\n\nThere are many tutorials to deploy a NodeJS package on AWS Lambda with API Gateway for e.g. [this](https://github.com/hasura/graphql-serverless/tree/master/aws-nodejs/apollo-sequelize#deployment). We will keep Lambda deployment out of the scope of this tutorial.\n\nAssuming you have deployed your Lambda succesfully, you would have received an HTTP endpoint for it. Update your Event Triggers with the new endpoints through the Hasura console or Hasura API and that's it.\n\n#### (Optional) Connection Pooling \n\nThe Lambda functions need database connections to execute their logic which cannot scale at the same rate as serverless invocations (as database connections are slow and costly). Hence, we need an external connection pooler to \"loadbalance\" the database connections.\n\nWith Postgres, we can add a standalone connection pooler like [pgBouncer](https://pgbouncer.github.io/) to accomplish this. \n\nFollow the guide [here](https://github.com/hasura/graphql-serverless/tree/master/aws-nodejs/apollo-sequelize#connection-pooling) to deploy pgBouncer in few clicks on a free EC2 instance using AWS Cloudformation. The output of the cloudformation template should give a new `POSTGRES_CONNECTION_STRING` which you can update in your Lambda to start using pgBouncer.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhasura%2F3factor-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhasura%2F3factor-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhasura%2F3factor-example/lists"}