typescript 131 lines · 3 tabs

Aggregate Daily Order Totals Into a Report With Knex and a Formatter

Shared by codesnips Sep 2026
3 tabs
import { Knex } from 'knex';

export interface RawDailyRow {
  day: string;
  order_count: string;
  gross_cents: string;
  refunded_cents: string;
}

export async function fetchDailyOrderTotals(
  db: Knex,
  from: Date,
  to: Date
): Promise<RawDailyRow[]> {
  const rows = await db('orders')
    .select(db.raw("DATE_TRUNC('day', created_at)::date AS day"))
    .count('* AS order_count')
    .select(db.raw('COALESCE(SUM(total_cents), 0) AS gross_cents'))
    .select(
      db.raw(
        "COALESCE(SUM(total_cents) FILTER (WHERE status = 'refunded'), 0) AS refunded_cents"
      )
    )
    // half-open interval [from, to): safe for day bucketing at midnight
    .where('created_at', '>=', from)
    .andWhere('created_at', '<', to)
    .groupByRaw("DATE_TRUNC('day', created_at)")
    .orderBy('day', 'asc');

  return rows as unknown as RawDailyRow[];
}
3 files · typescript Explain with highlit

This snippet shows how a daily sales report is assembled in a Node service: the raw aggregation lives in a query-builder layer, the rounding and shaping of money live in a formatter, and a thin service ties them together. Separating these concerns keeps SQL testable, keeps currency math out of the query, and makes the HTTP-facing shape stable even if the storage changes.

In orderReport.queries.ts, fetchDailyOrderTotals uses Knex to group orders by calendar day. DATE_TRUNC('day', created_at) collapses timestamps into day buckets, and the aggregate columns are built explicitly so the result set is predictable: COUNT(*) for order volume, SUM(total_cents) for gross revenue, and a filtered SUM that only counts refunded rows via FILTER (WHERE status = 'refunded'). Working in integer cents throughout avoids floating-point drift — a classic reporting bug where summed dollars slowly disagree with the ledger. The query is parameterized on a half-open [from, to) interval, which is the safe way to bucket dates because it never double-counts or misses rows at midnight boundaries. whereBetween is deliberately avoided since it is inclusive on both ends.

The raw rows come back as RawDailyRow, a shape that mirrors the database exactly (all cents, all strings from SUM). Postgres returns SUM as a string to preserve precision, so Number(row.gross_cents) conversion is done in one place rather than scattered across the codebase.

In orderReport.format.ts, formatDailyReport turns raw rows into DailyReportRow records meant for JSON output. centsToDecimal divides by 100 and fixes two decimals as a string, keeping the money representation exact for transport. formatDailyReport also computes an averageOrderValue guarded against division by zero, and fills gaps: buildDateSpine generates every day in the range so days with no orders still appear as zero rows rather than silently vanishing — important for charts and for spotting outages.

In OrderReportService, dailyReport orchestrates the flow: validate the range, run the query, format, and return a DailyReport with a summary total. assertValidRange rejects inverted or oversized windows before touching the database, which protects against accidental full-table scans. Because each layer has a single responsibility, the SQL can be unit-tested against a fixture database, the formatter can be tested with plain objects, and the service can be tested with a stubbed query function. This layering is the pattern to reach for whenever a report needs both correct aggregation and a stable, presentation-ready contract.


Related snips

Share this code

Here's the card — post it anywhere.

Aggregate Daily Order Totals Into a Report With Knex and a Formatter — share card
Link copied