6 min read
Agustinus Nathaniel

A Practical Guide to Data Standards for Seamless Collaboration and Data Integrity

Introduction

If you’ve ever worked on a product with multiple engineers across frontend and backend, you’ll know how quickly things can fall apart without shared rules for handling data. Small differences—like whether dates should be UTC or local, or if null and [] mean the same thing—can create bugs that are painful to debug but trivial to avoid. I’ve been in that situation many times, and over the years I’ve settled on a set of lightweight standards that keep things predictable. This post is about those standards: pragmatic, easy-to-adopt rules that have saved me (and my teams) countless hours.


Why Data Standards Matter

  • Consistency: Everyone interprets data the same way.
  • Interoperability: Frontend and backend don’t need to guess what a field means.
  • Maintainability: Less if/else spaghetti to handle edge cases.
  • Debugging: Issues are easier to track when the data shape is predictable.

Real-world example:

  • Date and Time: the backend sometimes stored dates in local time while the frontend assumed UTC. Users in different regions saw reports shift by a day.
  • An endpoint returned null for items, which immediately broke the UI. After we introduced clear standards, these problems disappeared.

Common Data Standards

1. Date and Time: The Timezone Trap

  • Always store in UTC on the server.
  • Use ISO 8601 format (2025-09-18T23:59:59Z).

    Note: You can also store dates as Unix timestamps (milliseconds since epoch). They’re compact and avoid parsing issues across environments. ISO strings are easier to read and debug. Pick one format and apply it consistently.

  • Let the frontend localize for display.
  • For ranges, store EOD (End of Day) as 23:59:59 and define the expected timezone.
  • For recurring events, store timezone metadata separately.

Example JSON:

{
  "start_date": "2025-09-01T00:00:00Z",
  "end_date": "2025-09-30T23:59:59Z",
  "timezone": "Asia/Jakarta"
}

Backend (Node.js):

const endDate = new Date("2025-09-30");
endDate.setUTCHours(23, 59, 59, 999);
res.json({ end_date: endDate.toISOString() });

Frontend (React):

import dayjs from "dayjs";
const displayDate = dayjs(new Date(end_date)).format("MMM d, yyyy");

2. Boolean: When null Breaks Logic

  • Client: default to treat null as false, unless there are special cases or agreements.
  • If true/false isn’t enough, use enums (e.g., pendingapprovedrejected).
  • Document when a flag means state vs. capability.

Example JSON:

{
  "is_active": false,
  "is_admin": true,
  "status": "approved"
}

Backend (Express):

const isVerified = user.isVerified ?? false;
res.json({ isVerified });

Frontend:

if (isVerified) {
  return <Dashboard />;
}
return <VerifyScreen />;

3. Arrays: Empty or Null? Pick One.

  • Default to return [] instead of null for empty array. Either as API response standard or the frontend always anticipate by defining fallbacks.
  • Keep array item types consistent (IDs, objects, not both).
  • Clarify whether ordering is backend or frontend responsibility.

Example JSON:

{
  "roles": ["admin", "editor"],
  "items": []
}

Backend (Node.js):

const items = (await db.getItems()) || [];
res.json({ items });

Frontend (React):

const items = fetchedItems ?? [];

{
  items.length > 0 ? (
    items.map((item) => <Item key={item.id} {...item} />)
  ) : (
    <p>No items found</p>
  );
}

In TypeScript or modern JS, you can simplify null handling with operators like ?? or ?.. For example:

const length = (array ?? []).length;
const safeValue = user?.isVerified ?? false;

Reference:


4. Numbers: Precision Matters

  • Store numeric values as numbers, not strings.
  • For money, use integers in smallest units (e.g., cents).

    Important: Avoid doing critical math in the browser — floating point quirks can cause rounding errors. Always let the backend be the source of truth for final financial calculations, and use the frontend mainly for display/formatting. If you must calculate in the browser, add reverse checks (e.g., verify a * b / b === a) to detect anomalies.

  • For precision (e.g., FX rates), use decimal strings.
  • Define min/max limits (e.g., percentages 0–100).

Example JSON:

{
  "price": 4999,
  "exchange_rate": "1.2345",
  "discount_percent": 15
}

Backend:

res.json({ priceInCents: 999, userId: "123e4567-e89b-12d3-a456-426614174000" });

Frontend:

import nstr from "nstr";
const price = nstr(priceInCents / 100, { maxDecimals: 2 }); // "9.99"

5. Strings: Keep Them Clean

  • Trim whitespace before saving.
  • Normalize casing where relevant (emails always lowercase).

    Trimming and lowercasing help, but watch out for homoglyphs and invisible Unicode characters (e.g., user@example.com vs uѕer@example.com with a Cyrillic “s”). For sensitive fields like emails and usernames, use String.prototype.normalize() or dedicated libraries to ensure consistency and security.

  • Use enums if the value should be constrained.

Example JSON:

{
  "name": "Nathan",
  "email": "nathan@example.com",
  "status": "active"
}

Backend:

const email = req.body.email.trim().toLowerCase();
await db.save({ email });

Frontend:

<input value={email} onChange={handleEmailChange} />

6. IDs and References: Don’t Mix Types

  • Use UUIDs or consistent numeric IDs.
  • Don’t mix "123" and 123.
  • Clarify whether IDs are internal-only or public-facing.
  • Prefer referencing by ID over embedding full objects unless necessary.

Example JSON:

{
  "user_id": "a12b3c4d-5678-90ef-1234-567890abcdef",
  "order_id": 1024
}

Backend:

res.json({ user_id: uuidv4() });

Frontend:

fetch(`/api/users/${user_id}`)
  .then((res) => res.json())
  .then((user) => setUser(user));

7. Error Handling: Predictable Responses

  • Standardize errors with:
    • code: machine-readable
    • message: human-readable
    • details: optional per-field errors
  • Align with proper HTTP status codes.

Example JSON:

{
  "code": "INVALID_INPUT",
  "message": "Email is not valid",
  "details": { "email": "Invalid format" }
}

Backend (Express):

res.status(400).json({
  code: "INVALID_INPUT",
  message: "Email is not valid",
  details: { email: "Invalid format" },
});

Frontend:

if (error.code === "INVALID_INPUT") {
  showToast(error.message);
}

Putting It All Together

Adopting standards isn’t about adding complexities—it’s about saving time. A few practical tips:

  1. Document early. Put conventions in README or API docs.
  2. Validate automatically. Use Zod, Yup, or backend schema validators.
  3. Enforce in code. Add type-checking and linter rules.
  4. Review often. Check API responses during code reviews.

These standards are small investments that pay off in stability and predictability.


Conclusion

Data standards aren’t glamorous, but they are the foundation of reliable systems. Whether it’s dates in UTC, booleans defaulting to false, or defaulting array values to empty array instead of null, these rules remove ambiguity and friction. The result: faster development, fewer bugs, and happier teams.

Start small. Document the basics. Enforce them in code. Over time, it’ll feel natural—and you’ll wonder how you ever worked without them.