Developing an app for household management with AWS Amplify

Toru Takahashi - Aug 20 - - Dev Community

Introduction

Hi everyone.

In this article, I’m going to introduce the household budgeting app I’ve developed and currently operate using AWS Amplify (referred to as "Amplify" from here on).

🚨Please note, this app is a personal project and is not intended for commercial use. It’s designed solely for use by my family and me.🚨

💡Original japanese post is here.(I wrote this too)💡
https://qiita.com/tttol/items/d79b5e67de4c33e27858

Intended Audience

  • Anyone looking to solve problems using web applications
  • Those who have heard of Amplify but don’t know the details
  • People who want to develop web applications using AWS resources easily
  • Those with app development knowledge but who are less confident in infrastructure

The problem of household management

“Our problem: Eliminating the hassle of reimbursements between family members”

My wife and I often talk about reimbursements. For example, if she goes shopping at the grocery store, she might temporarily cover the cost, and later I’ll pay her back for half of it.


Wife: “I went shopping and covered the cost, so please pay me back later.”
Me: “Thanks. Oh, by the way, I bought tissues at the pharmacy yesterday, so I’d like to offset that cost.”
Wife: “Hmm, but I’m planning to go to the store tomorrow, so maybe we can include that too…”
Me: “Ugh…” (This is where I get tired of thinking about it.)


Doing these reimbursements every day is a hassle, so I wanted a way to manage these situations better. The first method I thought of was using Google Spread Sheets.

Image description

Both my wife and I would record the amounts and items we paid for, and we’d use a SUMIF function in the last row to calculate the final amount each of us needs to pay. We didn’t need to settle the balance daily; we could do it weekly or monthly.

By sharing this spreadsheet between us, we aimed to make managing reimbursements easier.

Issues with Spreadsheet Management

Although managing things through a spreadsheet made the process somewhat easier, new issues arose.

  • It’s hard to enter data into the spreadsheet from a smartphone
    • When entering data while out, you have to use the mobile app version of Google Spread Sheets.
    • The screen is small…
    • Data entry becomes a hassle, and receipts start piling up.
  • When the table fills up, you need to add more rows, which is also hard on a smartphone.
    • As rows increase, scrolling becomes necessary.
  • It’s hard to see at a glance how much you owe.

As a result, I ended up doing most of the data entry on a PC at home when I had free time.

To solve these issues, I decided to turn the functionality of the spreadsheet into a web application.

Turning It into an App with Amplify

I built the application with Next.js and deployed it using Amplify Hosting.

Image description

The SUMMARY section at the top of the screen shows each person’s debt.

The area below that shows a list of items that have been reimbursed. You can see that two items, buy grocery and tissue, have been entered. Based on these two items, the app calculates that the husband owes nothing, while the wife has a debt of 1,051 yen. (The method for calculating debt will be explained later.)

You can add new items from the screen.

Image description

By improving the usability of the item creation screen, I addressed the issues with smartphone operation mentioned earlier. Dropdowns are used for label selection, a date picker for entering dates, and radio buttons for selecting the payer, making the interface more user-friendly.

How Debt is Calculated

  • It's hard to see at a glance how much you owe.

To address the issue mentioned above, the app displays a summary of each person’s debt at the top of the screen. This allows you to see your outstanding balance at a glance when you open the app.

The summary displays two items: Total NET debt and debt.Here’s what each of these represents:

  • Total NET debt – This shows the final amount one person owes after subtracting mutual unpaid amounts.
  • debt – This shows the total unpaid amount for each person before any offsets.

Let’s look at a specific example.

In the image, the husband has already covered 2,500 yen, while the wife has covered 398 yen. The husband needs to pay the wife half of 398 yen, which is 199 yen. The wife needs to pay half of 2,500 yen, which is 1,250 yen. These two amounts are shown under debt.

Subtracting these two values gives 1,250 - 199 = 1,051, meaning the wife ultimately owes the husband 1,051 yen.

Therefore, the Total NET debt is 0 yen for the husband and 1,051 yen for the wife.

debt Total NET debt
Husband ¥199 ¥0
Wife ¥1,250 ¥1,051

App Architecture

The architecture of the app is as follows. For those familiar with Amplify, this setup should be quite familiar.

Screenshot 2024-08-18 11.03.24.png

Hosting

I use Amplify Hosting. Under the hood, S3 and CloudFront are used to deliver the HTML content.

Screenshot 2024-08-18 11.06.08.png

The S3 bucket and CloudFront distribution used here cannot be viewed or edited. It seems that these resources are managed internally by AWS.

Database & API

I use DynamoDB as the database, where I store the items each person has paid for. CRUD operations are performed using AppSync with GraphQL.

In Amplify, a file called amplify/data/resource.ts is automatically generated, where you can define the DynamoDB architecture as IaC (Infrastructure as Code).

import { a, defineData, type ClientSchema } from '@aws-amplify/backend';

const schema = a.schema({
  Todo: a.model({
      content: a.string(),
      isDone: a.boolean()
    })
    .authorization(allow => [allow.group('Admin')]), // User-group based data access
});

// Used for code completion / highlighting when making requests from frontend
export type Schema = ClientSchema<typeof schema>;

// defines the data resource to be deployed
export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: 'apiKey',
    apiKeyAuthorizationMode: { expiresInDays: 30 }
  }
});
Enter fullscreen mode Exit fullscreen mode

As mentioned at the beginning, this app is only used by my wife and me, so proper access control for viewing and editing app data is essential. In data/resource.ts, I’ve set up access controls so that only users in a specific user group on Cognito can access DynamoDB. The relevant part is as follows:

const schema = a.schema({
  Todo: a.model({
      content: a.string(),
      isDone: a.boolean()
    })
    .authorization(allow => [allow.group('Admin')]), // User-group based data access
});
Enter fullscreen mode Exit fullscreen mode

The .authorization(allow => [allow.group('Admin')]), ensures that only users in the Admin group have access. (The Admin group is manually created in Cognito via the AWS Management Console beforehand.)

References:
https://docs.amplify.aws/react/build-a-backend/data/set-up-data/
https://docs.amplify.aws/react/build-a-backend/data/customize-authz/user-group-based-data-access/

Auth

Cognito is used for authentication.

Similar to the database, a file called amplify/auth/resource.ts is automatically generated, where you can define the authentication architecture.

import { defineAuth } from "@aws-amplify/backend"

/**
 * Define and configure your auth resource
 * @see https://docs.amplify.aws/gen2/build-a-backend/auth
 */
export const auth = defineAuth({
  loginWith: {
    email: true,
  },
})
Enter fullscreen mode Exit fullscreen mode

References:
https://docs.amplify.aws/react/build-a-backend/auth/set-up-auth/

Operating Costs

Amplify is billed on a pay-as-you-go basis.

https://aws.amazon.com/jp/amplify/pricing/

The operating cost of my household budgeting app is about 100 ~ 200 yen (≒ $1 ~ $1.5)per month. Since the active users are just my wife and me, it’s practically free.

Conclusion

Managing our budget with the app has become much easier compared to using a spreadsheet. The app still has some rough edges, and I occasionally find bugs, so I plan to maintain it at my own pace.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .