CodingNic

API, Persistence, and Hardening

Store Money as Integer Cents

API, Persistence, and Hardening 20 min read

Store Money as Integer Cents

The form displays amounts such as 84.50, but the application should not use decimal JavaScript numbers for financial arithmetic. JavaScript uses binary floating-point numbers, so some decimal values cannot be represented exactly.

For this project, the simple rule is: the stored value is an integer number of cents; the formatted value is only for display.

Open client/src/lib/finance.js and create the conversion used at the form/API boundary:

javascript
export function parseAmountToCents(value) {
  const text = String(value ?? "").trim();
  if (!text || !/^\d+(?:\.\d{1,2})?$/.test(text)) return null;

  const [whole, fraction = ""] = text.split(".");
  const cents = Number(`${whole}${fraction.padEnd(2, "0")}`);
  return Number.isSafeInteger(cents) && cents > 0 ? cents : null;
}

Here is what each part does:

  • String(value ?? "") makes the helper safe when the input is missing.
  • The regular expression allows whole amounts or amounts with one or two decimal places.
  • split(".") separates dollars from cents.
  • padEnd(2, "0") turns 5 cents into 50 cents.
  • Joining the two strings gives an integer such as 8450 for $84.50.
  • Number.isSafeInteger() prevents values outside JavaScript’s safe integer range.
  • > 0 rejects zero and negative values because this application records positive transaction amounts.

Then change the transaction object so it carries amountCents instead of amount. The UI can turn that integer back into an input value with centsToInput() and can display it with formatCents().

The server must enforce the same shape. Open server/src/domain/transactions.js and validate:

javascript
if (!Number.isSafeInteger(amountCents) || amountCents <= 0) {
  return "amountCents must be a positive integer";
}

The server is the final authority because a request can arrive without using this React form.

The project also contains older sample data that used an amount field. The persistence layer migrates that value once by multiplying it by 100 and then removes the old property. That lets an existing learner database move to the new model without manually rewriting every record.

Test it

Run the application and create an expense for 84.50. Inspect the stored JSON file and confirm the transaction contains:

text
amountCents: 8450

Also verify that 84.567 is rejected by the form or validation helper.

Checkpoint

You can now explain the difference between a display amount such as 84.50 and the stored value 8450, and every layer of the application uses the integer-cent representation.