Home
Softono
moneyapp-api

moneyapp-api

Open source TypeScript
13
Stars
2
Forks
0
Issues
2
Watchers
3 years
Last Commit

About moneyapp-api

MoneyApp API is a versatile data analytics backend designed for personal finance management applications. Built with TypeScript on the NestJS framework, it exposes a flexible GraphQL interface using Apollo Server, enabling efficient data interaction for front-end visualizations. The system utilizes Prisma ORM to manage data modeling and connects to a PostgreSQL database hosted on Supabase, migrating from an initial Notion API integration to overcome rate limits and ensure data consistency. Security is robust, employing Auth0 for identity management with a custom authentication guard that enforces role-based access control (RBAC) via JWT tokens. The API supports comprehensive financial queries, including filtering income and expenses by various fields, applying date-range filters, calculating totals and average daily metrics, and grouping data with zero-value date padding for accurate chart rendering. Infrastructure includes Heroku for API hosting and CircleCI for continuous integration and deployment. While c

Platforms

Web Self-hosted

Languages

TypeScript

Links

Nest Logo

MoneyApp API

๐Ÿ•ต๏ธโ€โ™‚๏ธ About

Versatile GraphQL API built on top of NestJS framework for ease of use on data-analytics frontends

๐Ÿ“ Purpose

  1. Acting an the API for a finance management app (personal use)
  2. Provide a flexible way for the front-end to interact with data
  3. Learn about NestJS & GraphQL without copying from boring tutorials

๐Ÿ“š Frontend repo: HERE

๐Ÿ› ๏ธ Technologies used

Purpose Tools
โœ… Language TypeScript
โœ… Framework NestJS
โœ… GraphQL API Apollo Server
โœ… Data source Notion API
โœ… Data modelling Prisma ORM
โœ… Decode JWT jsonwebtoken
โœ… Secure API keys dotenv
โœ… Currency conversion Exchange Rates API

๐Ÿ› ๏ธ Infrastructure

No. Purpose Tools
1 Databse system PostgresSQL
2 Hosting database Supabase
3 Authentication & Identity management Auth0
4 API hosting Heroku
5 CD/CI CircleCI

๐Ÿ—๏ธ Architecture

image

โ›ฐ๏ธ Product roadmap

No. Description Status
1. Query any income data by any field โœ…
2. Set limit on query amount on income โœ…
3. Query any expenses data by any field โœ…
4. Set limit on query amount on expenses โœ…
5. Group income by any field and calculate sum & counts with date-range filter โœ…
6. Group income by date with date-range filter so that the data returned also includes dates with $0 income for data-visualisation โœ…
7. Group expenses by any field and calculate sum & counts with date-range filter โœ…
8. Group expenses by date with date-range filter so that the data returned also includes dates with $0 expenses for data-visualisation โœ…
9. Query average daily income with date-range filter โœ…
10. Query average daily expenses with date-range filter โœ…
11. Query total income with date-range filter โœ…
12. Query total expenses with date-range filter โœ…
13. Query net income with date-range filter โœ…
14. Prevent authenticated BUT unauthorised users from accessing ALL endpoints โœ…
14. Automatically add new users into a User table in DB after they signed up the app using Auth0
15. Link up Income and Expense tables with User by introducing association rules
16. Mutation for creating row for Income
17. Mutation for updating row for Income
18. Mutation for creating row for Expense
19. Mutation for updating row for Expense
20. Subscription with pagination for retrieving rows from Income
21. Subscription with pagination for retrieving rows from Expense

โ‰๏ธ Challenges & workarounds

No. Problem Solution
1 Rate limit from Notion API + Ugly & Inconsistent response structure from Notion SDK Migrate table to real DB (PostgreSQL)
2 DB data isn't in sync with data from Notion Need Notion webhook to setup triggers but none available, current plan is to manually update DB from time to time
3 There are no out-of-box auth solutions for Nest + GraphQL + Auth0 in RBAC (Role-based access-control) Implemented a custom guard that transforms request context from REST into GraphQL context then authenticate and authorise access based on the permissions field of the decoded jwt token payload.
4 When returning sum from IncomeGroupBy queries, the sum amount does not reflect the differences in currency (NZD and USD) Need to either setup compulsory currency filter in query layer or auto-calculate all USD amount to NZD by real-time exchange rate on return
5 Need to show dates with $0 income for aggregated income queries by dynamic date-range filter for time-series chart display Used dates API to populate empty dates

๐Ÿ›ซ Running the app

# Installation
$ yarn

# build
$ yarn build

# development
$ yarn start

# watch mode
$ yarn dev

# production mode
$ yarn start:prod


## Test
# unit tests
$ yarn test

# unit tests - auto-update
$ yarn test:watch

# e2e tests
$ yarn test:e2e

# test coverage
$ yarn test:cov

## Database
# database seeding
$ npx prisma db seed

# See DB using prisma studio
$ npx prisma studio

๐Ÿ”ญ Sample query & response:

All queries are protected by guards, meaning only authorised users are able to execute the queries (which is me, myself and I)

Query income by payment method and calculate sum of income by payment method

# Note: endDate and dateStartInc are optional.
# When date filters are not provided, the query will return all records`
query {
  incomeGroupBy ( 
    field: "paymentMethod",
    valueType: "sum"
    endDate: "Sun Nov 21 2021 12:12:28 GMT+1300 (New Zealand Daylight Time)"
    startDate: "Wed Sep 01 2021 12:12:28 GMT+1200 (New Zealand Standard Time)"
  ) {
    incomePaymentMethod
    sum
  }
}
{
  "data": {
    "incomeGroupBy": [
      {
        "incomePaymentMethod": "Cash",
        "sum": 10070.43
      },
      {
        "incomePaymentMethod": "Paypal",
        "sum": 10222.8
      },
      {
        "incomePaymentMethod": "Direct Debit",
        "sum": 32190.28
      }
    ]
  }
}

Query income by payment method and returning the number of times income is received by each payment method

query {
  incomeGroupBy ( 
    field: "paymentMethod",
    valueType: "count"
  ) {
    incomePaymentMethod
    count
  }
}
{
  "data": {
    "incomeGroupBy": [
      {
        "incomePaymentMethod": "Bitcoin",
        "count": 28
      },
      {
        "incomePaymentMethod": "Polkadot",
        "count": 35
      },
      {
        "incomePaymentMethod": "Ethereum",
        "count": 41
      }
    ]
  }
}

Query income by "paidBy" and returning sum of each person/org who paid

query {
  incomeGroupBy ( 
    field: "paidBy",
    valueType: "sum"
  ) {
    incomePaidBy
    sum
  }
}
{
  "data": {
    "incomeGroupBy": [
      {
        "incomePaidBy": "Amazon Inc",
        "sum": 332830
      },
      {
        "incomePaidBy": "Google Inc",
        "sum": 312872
      },
      {
        "incomePaidBy": "Salesforce Inc",
        "sum": 3298770
      },
    ]
  }
}

Query income by date and returning accumulated income including days when income is $0

query {
  incomeGroupBy ( 
    field: "date",
    valueType: "sum"
    startDate: "Sun Nov 21 2021 12:12:28 GMT+1300 (New Zealand Daylight Time)"
    endDate: "Tue Nov 23 2021 13:00:00 GMT+1300 (New Zealand Daylight Time)"
  ) {
    date
    sum
  }
}
{
  "data": {
    "incomeGroupBy": [
      {
        "date": "2021-11-21T23:12:28.000Z",
        "sum": 23443
      },
      {
        "date": "2021-11-22T23:12:28.000Z",
        "sum": 0
      },
      {
        "date": "2021-11-23T00:00:00.000Z",
        "sum": 20000
      },
    ]
  }
}

Query daily average income given a date range, returning the type of average income queried and the value

query {
  averageIncome (
    type: "daily"
    startDate: "Thu Jul 01 2021 12:00:00 GMT+1200 (New Zealand Standard Time)"
    endDate: "Tue Sep 28 2021 13:00:00 GMT+1300 (New Zealand Daylight Time)"
  ) {
    type
    average
  }
}
{
  "data": {
    "averageIncome": [
      {
        "type": "daily",
        "average": 5000
      }
    ]
  }
}

Get total income given a date range

query {
  incomeSum (
    startDate: "Thu Jul 01 2021 12:00:00 GMT+1200 (New Zealand Standard Time)"
    endDate: "Tue Sep 28 2021 13:00:00 GMT+1300 (New Zealand Daylight Time)"
  ) {
    sum
  }
}
{
  "data": {
    "incomeSum": [
      {
        "sum": 152360.76
      }
    ]
  }
}

Get total expenses given a date range

query {
  expenseSum (
    startDate: "Thu Jul 01 2021 12:00:00 GMT+1200 (New Zealand Standard Time)"
    endDate: "Tue Sep 28 2021 13:00:00 GMT+1300 (New Zealand Daylight Time)"
  ) {
    sum
  }
}
{
  "data": {
    "expenseSum": [
      {
        "sum": 7639.89
      }
    ]
  }
}