- Table of Contents
- Environment Variables
- Installation
- Linting
- Scripts
- Github Actions
- API Documentation
- Utility Methods
- Middleware
- User Routes - /user
- Payment
- Transactions
- Reminders
Copy the .env.example file, and rename it to .env.
PORT="6000"
POSTGRES_PASSWORD=postgres
POSTGRES_USER=postgres
POSTGRES_DB=tamaraThis configuration works as-is. However, it can be customized to fit your needs.
Before installation, make sure you have setup the environment variables correctly.
On DockerHub, a production-ready container is available.
To build the container needed, use yarn docker:build.
Afterwards, you have two options:
yarn docker:devto run in Development Modeyarn docker:startto run in Production Mode
Alternatively, you can manually start the server with the following scripts:
yarninstall all dependencies.yarn devstart the server in development mode.yarn buildcompile the Typescript files.yarn startstart the server in production mode.yarn testrun Unit & Integration tests.
However, these do not create a database instance.
The entire project is linted with eslint. The settings.json file contains the settings needed to automatically lint on save.
To configure the linter, open the eslintrc.json file.
Upon running Github Actions, the project is linted to ensure the code is up to standard.
A script can be run by yarn <script>
buildBuilds the Typescript files into the/distfolder.build:cleanCleans the\distfolder.build:watchRe-Builds upon any change to the files.devRuns the server and watches for any changes.startruns the compiled server inproductionmode. Compile first withbuild!lintLints the project.lint:fixLints the project and fixes any errors.testTests the application Rundocker:testfirst!migration:createCreate a new Database Migration file.migration:generateAutomatically Generate a new Database Migration.docker:buildBuild the server's docker container.docker:devRuns the server's docker container in development mode, watching for any changes.docker:testCreates the database for the tests.docker:prodRuns the server's docker container in production mode.docker:downTurns off all active containers.
Upon pushing to GitHub code is linted, tested, and checked if it compiles. Afterwards, a Docker image is built and pushed to DockerHub.
All Neccessary .env variables are checked and sanitized by this method.
Below are the available variables which are loaded:
interface Config {
PORT: number
DOCKER: boolean
POSTGRES_DB: string
POSTGRES_USER: string
NODE_ENV: 'production' | 'test' | 'development'
POSTGRES_PASSWORD: string
DUE_AFTER: TimeValues
}To access these variables import it like the following:
import { config } from './util'A logger is available by importing it like the following:
import { logger } from './util'The logger can be customized from the utility file. By default, the logger is customized as the following:
// Function that does nothing (No Operation)
const noop = (..._data: unknown[]) => { }
const logger = new Logger({
onLog: config.NODE_ENV !== 'development' ? noop : console.log,
onInfo: config.NODE_ENV === 'test' ? noop : (...data) => console.log(...data),
onError: (...data) => console.error(...data),
})onErrorwill always be defined.onInfowill not log in atestenvironment.onLogwill only log in adevelopmentenvironment.
This allows the flexibility to quickly change what is being logged for the entire server, and may be integrated with logging services in the future.
All routes in the API which contain a JSON response sends a variant of ResponseData. Either a data field is defined, or an errors field is defined.
If the data field is defined, it will be of type T, which is the generic type of ResponseData. By default, it is a string.
{
"data": T
}If the errors field is defined, it will be an array of one three types:
Result<ValidationError>ResponseErrorServerError
All elements in the errors array have these values defined:
{
param: ...
msg: ...
location: ...
}paramIs the parameter which caused the error.msgIs a description of the error.locationis where the error has occurred.
The errors array may return multiple error elements, such as the following for example:
{
"errors": [
{
"msg": "Duplicate Name",
"param": "name",
"location": "body"
},
{
"msg": "Invalid ID",
"param": "userId",
"location": "params"
}
]
}For the ServerError, it is the only type which has a location of error. The other two are interchangable.
interface ResponseError {
param: string
msg: string | Error
location: 'body' | 'cookies' | 'headers' | 'params' | 'query'
}
interface ServerError {
param: string
msg: string | Error
location: 'error'
}
interface ResponseData<T = string> {
data?: T
errors?: Result<ValidationError>[] | ResponseError[] | ServerError[]
}All utility middleware used is contained here.
All routes are logged with a custom logger middleware. Afterwards, it is passed onto the next middleware.
- Uses the logger's
logmethod.
Routes are speed-limited with express-slow-down to prevent DDoS attacks.
{
// How long a request is is stored in milliseconds.
// Here, it is set to 15 minutes.
windowMs: 15 * 60 * 1000,
// The number of requests made before any slowdown occurs.
// Here, it is set to 100 requests.
delayAfter: 100,
// The amount of time in 'ms' added after each request exceeding 'delayAfter'
delayMs: 500,
}This middleware responds with any expected errors found by express-validator's middleware.
This middleware handles and logs any uncaught errors which occur within the API. express-async-errors directs any unhandled errors to this route.
In a production NODE_ENV, this is the JSON sent:
{ errors: [{ 'error': 'Server Error.' }]}However, in test and development environments, the error itself is returned instead of Server Error.
{ errors: [{ 'error': err }] }- Returns a
500response code. - Uses the logger's
errormethod.
This route is used to check if the API has been setup properly, and for health checks.
- Returns a
200response code.
- Returns a
200response code. - Returns a
JSONresponse:ResponseData<string>
{
"data": "OK"
}
If no route for the request has been found, the server will run this middleware.
-
Returns a
404response code. -
Returns a
JSONresponse:ResponseData{ errors: [ { "location": "error",}] }
Requires a body:
{
"name": string
}Where name is a unique string between 3 and 20 characters long.
On a Valid Request:
- Returns a
201response code. - Returns a
JSONresponse:ResponseData<User>
{
"data": {
"name": "Joe",
"userId": 1,
"createdAt": "2021-05-02T06:48:01.905Z",
"updatedAt": "2021-05-02T06:48:01.905Z"
}
}Errors: Read here for Error body description.
400Bad Request- Invalid
name, must be astringbetween 3 and 20 characters long. - Duplicate
name.
- Invalid
- Always returns a
200response code. - Returns a
JSONresponse:ResponseData<User[]>
{
"data": [
{
"userId": 2,
"name": "Ryan",
"createdAt": "2021-05-02T06:49:16.818Z",
"updatedAt": "2021-05-02T06:49:16.818Z",
"payments": [],
"paymentHistory": []
},
{
"userId": 1,
"name": "Joe",
"createdAt": "2021-05-02T06:48:01.905Z",
"updatedAt": "2021-05-02T06:48:01.905Z",
"payments": [],
"paymentHistory": []
}
]
}Requires the userId param.
On a Valid Request:
- Returns a
200response code. - Returns a
JSONresponse:ResponseData<User>
{
"data": {
"name": "Joe",
"userId": 1,
"createdAt": "2021-05-02T06:48:01.905Z",
"updatedAt": "2021-05-02T06:48:01.905Z"
}
}Errors: Read here for Error body description.
400Bad Request- Invalid
userId, it must be anumber.
- Invalid
404Not Found- The
userIdwas not found.
- The
Requires a body:
{
"userId": number,
"amount": number,
"currency": Currency,
}List of currencies supported here
On a Valid Request:
- Returns a
201response code. - Returns a
JSONresponse:ResponseData<string>
{
"data": "Successfully created a new Payment"
}Errors: Errors: Read here for Error body description.
400Bad Request- Invalid
userId, it must be anumber. - Invalid
amount, it must be anumber. - Invalid
currency, it must be a in the list of supported currencies.
- Invalid
404Not Found- The
userIdwas not found.
- The
Requires the parameter :userId
Retrieves a list of all a user's current payments.
On a Valid Request:
- Returns a
201response code. - Returns a
JSONresponse:ResponseData<Payment>
{
"data": [
{
"paymentId": 1,
"currency": "AUD",
"amount": "200.0000000000",
"paymentStatus": "underway",
"createdAt": "2021-05-02T07:57:50.185Z",
"updatedAt": "2021-05-02T07:57:50.185Z",
"dueAt": "2021-05-09T07:57:50.181Z",
"paidAt": null,
"deleted": false,
"sentDueReminder": false,
"paymentHistory": [
{
"paymentRecordId": 1,
"recordKind": "creation",
"amount": "200.0000000000",
"currency": "AUD",
"createdAt": "2021-05-02T07:57:50.205Z"
}
]
}
]
}Errors: Read here for Error body description.
400Bad Request- Invalid
userId, it must be anumber.
- Invalid
404Not Found- The
userIdwas not found.
- The
Requires a body
{
"userId": 1,
"paymentId": 4,
"amount": 250,
"currency": "AUD"
}- Returns a
201response code. - Returns a
JSONresponse
{
"data": {
"message": "Paid off Payment #4.",
"balance": 0,
"overcharge": 50,
"paymentStatus": "paid"
}
}message will be a message denoting:
- how much has been paid if there is a remaining balance
- If the payment is complete, it will say it has been paid off.
balance is the amount remaining from the payment
overcharge is the amount given back to the user if they have paid too much
paymentStatus is the status of the payment,
underwayif it is still not completely paidpaidif it has been paid offdueif payment is due
Request body:
{
"userId": 1,
"paymentId": 1
}
- Returns a
200status - Returns a
JSONresponse:
{
"data": "This payment has been deleted."
}
Errors:
- Returns a
404response if payment is not found.
Requires userId parameter
- Returns a
200response - Returns
JSON:ResponseData<PaymentRecord[]>
Requires a :userId parameter
- returns a
200response - Returns
JSON: ResponseData<Payment[]>