Implementation of an Express server based on Hexagonal (aka. Ports and Adapters) architecture, along with Supabase SQL DB and Stripe for payments.
- Intro
- Demo
- Features (done)
- Features (TBA)
- Technologies
- API
- Security
- Getting started
- Resources
- License
Sibling repo of Galactic Adventures - Client, make sure you check both out!
So, what is this exactly?
Imagine a travelling app... except it's in SPACE! And since I am optimistic about our future, let's assume you can travel anywhere; to any planet, any galaxy, heck, even other superclusters! Sounds awesome, right?
The issue with representing something like this in a relational database is exactly that, the relationships. Planets may be part of solar systems, however they may also be rogue. Space stations may reside close to a planet or they could be in some faraway nebula. Some solar systems are binary, some are trinary. Things can easily escalate out of proportion without any clear indication as to what is part of what.
Now, you might be wondering, but why did you not use a graph database? And the honest answer would be, I did not think about it until I was well-down the rabbit hole. Then, presented with the choice of learning an entirely new type of DB or adapting something I had a grasp on to my needs, I chose the latter.
Let me introduce you to the solution: the adjacency list pattern (pictured below).
waypoints - Stores all the parent and child nodes, as well as shared columns.
way_planets - Holds planet-specific data. Some column duplication (waypoint_code) is required to make the values more human-readable.
ext_way_atmospheres - An extension table which does not contain entities on its own.
meta_star_spectral_classes - A metadata table containing static data relating to the star classes.
IMPORTANT: Please have patience as the first request may take up to one minute. That's because on Render's free hosting plans the servers spin down after 15 minutes of inactivity.
- Ping: https://wolfpilot-galactic-adventures-server.onrender.com/ping
- Waypoint - Mars: https://wolfpilot-galactic-adventures-server.onrender.com/waypoints/9
- Adventure - Moon: https://wolfpilot-galactic-adventures-server.onrender.com/products/adventures/1
- Payment - Pluto : https://wolfpilot-galactic-adventures-server.onrender.com/payment/intent/pi_3Q0rb3IOgdrQNMoY1YNx5MrS
For more details and examples, see API.
Postman
-
Architecture
- Hexagonal
- DIP (Dependency Inversion Principle)
- Adjacency list pattern
- RESTful API
- CRUD
-
Middleware
- Debug console logger
- Request and error loggers with daily rotated files
- Request body content type validation
- Schema-based error validation
- Rate limited
-
Config
- Import path aliases using tsconfig-paths
- Localised .env files using dotenv-flow
-
QOL (Quality of Life)
- Automatically sync DB schema on pre-commit
- Custom HTTP, Service, Repository errors
- Keep-alive cronjob
- Stripe webhooks for post-payment processing
- Price calculator service per distance travelled
- New products: merchandise and tours
- User authentication
- Elastic search
- Languages
- Server
- Database
- Payment
- Config
- Linting & formatting
- Validation
- Rate limiting
- Testing
- Logging
- CI/CD
Either start your own server and visit http://localhost:9000 or https://wolfpilot-galactic-adventures-server.onrender.com.
# Ping
GET /ping - check server status
# Waypoints
GET /waypoints - fetch default waypoint
GET /waypoints/:id - fetch a single waypoint
# Products
GET /products?type={productType}&id={productId} - fetch a single product
GET /products/adventures/:id - fetch a single adventure
# Payment
GET /payment - fetch the Stripe Public Key
POST /payment/intent - create a new Stripe Payment Intent
GET /payment/intent/:id - fetch an existing Payment Intent
Simply copy-paste the following examples in your bash terminal making sure to separate newlines with "\" on Mac or "^" on Windows.
If running locally, please use "http://localhost:9000" instead. Alternatively, import the collection below in your own Postman instance and have some fun!
- GET /ping
curl \
-H 'Content-Type: application/json' \
-X GET 'https://wolfpilot-galactic-adventures-server.onrender.com/ping'
- GET /waypoints/null
curl \
-H 'Content-Type: application/json' \
-X GET 'https://wolfpilot-galactic-adventures-server.onrender.com/waypoints/null'
- GET /waypoints:id
curl \
-H 'Content-Type: application/json' \
-X GET 'https://wolfpilot-galactic-adventures-server.onrender.com/waypoints/4'
- GET /products?type={productType}&id={productId}=
curl \
-H 'Content-Type: application/json' \
-X GET 'http://localhost:9000/products?type=adventure&id=1'
- GET /products/adventures/:id
curl \
-H 'Content-Type: application/json' \
-X GET 'https://wolfpilot-galactic-adventures-server.onrender.com/products/adventures/1'
- POST /payment/intent
curl \
-H 'Content-Type: application/json' \
-X POST 'https://wolfpilot-galactic-adventures-server.onrender.com/payment/intent' \
-d '{
"productType": "adventure",
"productId": "1"
}'
Body:
productType
type: "adventure" | "merchandise" | "tour"
productId
type: string
- GET /payment/intent/:id
curl \
-H 'Content-Type: application/json' \
-X GET 'https://wolfpilot-galactic-adventures-server.onrender.com/payment/intent/pi_3Pz0UxIOgdrQNMoY1CEor4ck'
Broadly speaking, security concerns can be divided into three parts:
-
API abuse, such as DDOS attacks.
- Implementing a rate limiter at max 1000 req/min.
-
Database abuse, such as unauthorised access, SQL injection and leaks in logging.
- Enabling Supabase RLS (Row Level Security) with the only custom policy set to SELECT.
- Using Supabase JS client which sanitises all queries automatically.
- Parsing all errors, including PG, before they are thrown to the logger middleware.
- Ignoring all .env file configs (except for the example) from being commited.
- Returning error 5xx for server-related errors without any additional context.
-
Payment gateway abuse, such as XSS/CSRF attacks and key leaks.
- Validating all requests against unexpected inputs via JSON schema.
- Minimising possible attack areas by using popular, verified open-source 3rd party libs.
Realistically, there will always be unknown vectors of attack as no API is truly 100% secure and, as is often the case, with more complexity comes more risk.
For the scope of this project (and for the sake of me getting some sleep), let's call this good enough :)
$ git clone https://github.com/wolfpilot/galactic-adventures-server.git
$ cd galactic-adventures-server
$ pnpm install
# 1. Update your env config, see .env.example for guidance.
# 2. Run the API service
$ pnpm start:dev
- Recommended reading for anyone new to Building a REST API: A Hexagonal Approach by Christian Inyekaka
- An intro to Polymorphic Relationships and how they work by Hendel Ramzy
- Example repo for Hexagonal Architecture by David Lee
- Data sourced from NASA, Wikipedia and a dozen others
- Photography and generated image credits go to NASA
This project is licensed under the MIT License.