← All @molecule/* packages · App templates
@molecule/api-resource-medicationAPI resource · resource-medication · API (Node) · v1.0.1 · Apache-2.0
Medications + dosing + adherence tracking
npm install @molecule/api-resource-medication@molecule/api-resource-medication is an API resource: the routes, validation and storage for resource-medication, built on the database and auth cores so it runs on whichever providers your app has bonded.
import { createMedicationRouter } from '@molecule/api-resource-medication'
app.use('/medications', createMedicationRouter())
// GET / · POST / · GET /adherence?from=…&to=… · GET|PUT|DELETE /:id
// GET /:id/logs · POST /:id/logsWorks with: @molecule/api-bonds-default-express, @molecule/api-database, @molecule/api-i18n, @molecule/api-middleware-validation
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
@molecule/api-resource-medication — medication tracking with
dosing schedule + adherence log + adherence-rate calc.
Owner-scoped: every medication and dose log belongs to the authenticated
user. Medications carry dosage, frequency (e.g. daily, twice_daily),
times_of_day, and active date range; dose logs record
taken/skipped/late/missed and adherenceRate aggregates them over a date
range.
import { createMedicationRouter } from '@molecule/api-resource-medication'
app.use('/medications', createMedicationRouter())
// GET / · POST / · GET /adherence?from=…&to=… · GET|PUT|DELETE /:id
// GET /:id/logs · POST /:id/logs
import { adherenceRate, createMedicationForOwner, logDose } from '@molecule/api-resource-medication'
const med = await createMedicationForOwner(userId, {
name: 'Metformin',
dosage: '500 mg',
frequency: 'twice_daily',
})
await logDose(med.id, userId, { status: 'taken' })
const { rate } = await adherenceRate(userId, '2026-01-01', '2026-01-31')
resource
npm install @molecule/api-resource-medication @molecule/api-bonds-default-express @molecule/api-database @molecule/api-i18n @molecule/api-middleware-validation express zod
npm install -D @types/express
MedicationLogRowDatabase row shape for a medication intake log entry.
interface MedicationLogRow {
id: string
medication_id: string
owner_id: string
taken_at: string | Date
status: 'taken' | 'skipped' | 'late' | 'missed'
notes: string | null
created_at: string | Date
}
MedicationRowDatabase row shape for a medication record.
interface MedicationRow {
id: string
owner_id: string
name: string
generic_name: string | null
dosage: string
unit: string | null
frequency: MedicationFrequency
times_of_day: string[]
start_date: string | Date | null
end_date: string | Date | null
notes: string | null
is_active: boolean
created_at: string | Date
updated_at: string | Date
}
MedicationFrequencyDescribes how often a medication is taken (e.g. daily, twice daily, as needed).
type MedicationFrequency =
| 'once'
| 'daily'
| 'twice_daily'
| 'three_times_daily'
| 'four_times_daily'
| 'as_needed'
| 'weekly'
| 'custom'
adherenceRate(ownerId, from, to)Adherence summary: percentage of taken logs out of all logs in window.
function adherenceRate(
ownerId: string,
from: string,
to: string,
): Promise<{ taken: number; total: number; rate: number }>
createMedicationForOwner(ownerId, data)Creates a new medication record owned by the given owner and returns the persisted row.
function createMedicationForOwner(
ownerId: string,
data: {
name: string
generic_name?: string | null
dosage: string
unit?: string | null
frequency?: MedicationFrequency
times_of_day?: string[]
start_date?: string | null
end_date?: string | null
notes?: string | null
},
): Promise<MedicationRow>
createMedicationRouter()Creates and returns the Express router for medication CRUD and dose-logging endpoints.
function createMedicationRouter(): Router
deleteMedicationForOwner(medicationId, ownerId)Deletes a medication by ID, returning true on success or false if not found or not owned.
function deleteMedicationForOwner(medicationId: string, ownerId: string): Promise<boolean>
getMedicationForOwner(medicationId, ownerId)Fetches a single medication by ID, returning null if not found or not owned by the given owner.
function getMedicationForOwner(medicationId: string, ownerId: string): Promise<MedicationRow | null>
listLogs(medicationId, ownerId, opts?)Returns dose logs for a medication within an optional time window, or null if the medication is not found or not owned.
function listLogs(
medicationId: string,
ownerId: string,
opts?: { from?: string; to?: string; limit?: number },
): Promise<MedicationLogRow[] | null>
listMedicationsForOwner(ownerId, opts?)Returns all medications belonging to the given owner, optionally including inactive ones.
function listMedicationsForOwner(
ownerId: string,
opts?: { include_inactive?: boolean },
): Promise<MedicationRow[]>
logDose(medicationId, ownerId, data)Log a dose. If status not provided, infers from taken_at vs scheduled times.
function logDose(
medicationId: string,
ownerId: string,
data: {
taken_at?: string
status?: 'taken' | 'skipped' | 'late' | 'missed'
notes?: string | null
},
): Promise<MedicationLogRow | null>
updateMedicationForOwner(medicationId, ownerId, patch)Applies a partial patch to a medication, returning the updated row or null if not found or not owned.
function updateMedicationForOwner(
medicationId: string,
ownerId: string,
patch: Partial<MedicationRow>,
): Promise<MedicationRow | null>
LOG_STATUSESValid status values for a medication dose log entry.
const LOG_STATUSES: readonly ['taken', 'skipped', 'late', 'missed']
logCreateSchemaZod schema for validating medication dose log creation request payloads.
const logCreateSchema: z.ZodObject<
{
taken_at: z.ZodOptional<z.ZodString>
status: z.ZodOptional<
z.ZodEnum<{ taken: 'taken'; skipped: 'skipped'; late: 'late'; missed: 'missed' }>
>
notes: z.ZodOptional<z.ZodNullable<z.ZodString>>
},
z.core.$strip
>
MEDICATION_FREQUENCIESValid dosing frequency options for a medication schedule.
const MEDICATION_FREQUENCIES: readonly [
'once',
'daily',
'twice_daily',
'three_times_daily',
'four_times_daily',
'as_needed',
'weekly',
'custom',
]
medicationCreateSchemaZod schema for validating medication creation request payloads.
const medicationCreateSchema: z.ZodObject<
{
name: z.ZodString
generic_name: z.ZodOptional<z.ZodNullable<z.ZodString>>
dosage: z.ZodString
unit: z.ZodOptional<z.ZodNullable<z.ZodString>>
frequency: z.ZodOptional<
z.ZodEnum<{
once: 'once'
daily: 'daily'
twice_daily: 'twice_daily'
three_times_daily: 'three_times_daily'
four_times_daily: 'four_times_daily'
as_needed: 'as_needed'
weekly: 'weekly'
custom: 'custom'
}>
>
times_of_day: z.ZodOptional<z.ZodArray<z.ZodString>>
start_date: z.ZodOptional<z.ZodNullable<z.ZodString>>
end_date: z.ZodOptional<z.ZodNullable<z.ZodString>>
notes: z.ZodOptional<z.ZodNullable<z.ZodString>>
},
z.core.$strip
>
medicationUpdateSchemaZod schema for validating medication update request payloads; all create fields become optional and is_active is added.
const medicationUpdateSchema: z.ZodObject<
{
name: z.ZodOptional<z.ZodString>
generic_name: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
dosage: z.ZodOptional<z.ZodString>
unit: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
frequency: z.ZodOptional<
z.ZodOptional<
z.ZodEnum<{
once: 'once'
daily: 'daily'
twice_daily: 'twice_daily'
three_times_daily: 'three_times_daily'
four_times_daily: 'four_times_daily'
as_needed: 'as_needed'
weekly: 'weekly'
custom: 'custom'
}>
>
>
times_of_day: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>
start_date: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
end_date: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
notes: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
is_active: z.ZodOptional<z.ZodBoolean>
},
z.core.$strip
>
Peer dependencies:
@molecule/api-bonds-default-express ^1.0.1@molecule/api-database ^1.0.1@molecule/api-i18n ^1.0.1@molecule/api-middleware-validation ^1.0.1express ^5.0.0zod ^4.0.0@molecule/api-bonds-default-express@molecule/api-database@molecule/api-i18n@molecule/api-middleware-validationexpresszodTables: src/__setup__/medications.sql creates medications +
medication_logs. An mlcl-scaffolded API replays __setup__/*.sql
automatically on migrate; anywhere else run it once — nothing at runtime
creates them.
The router does not authenticate — it reads the caller from
res.locals.session (populated by your global auth middleware) and 401s
without a session. All service functions are …ForOwner(…, ownerId) and
return null for rows the caller doesn't own — always pass the
AUTHENTICATED user's id, never a client-sent one.
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. This is HEALTH data: a box you can't check is a correctness or privacy bug to fix, never a skip:
name, dosage,
frequency, times_of_day — and the med then appears in the user's list
(GET /) carrying those exact values, not a truncated or defaulted copy.twice_daily med carries two
times_of_day entries and the UI shows two dose slots for the day — not one
and not three; a daily med shows exactly one. (Nothing derives the times
from frequency automatically, so a mismatch is a real bug to catch here.)POST /:id/logs with status: 'taken' (and its taken_at) then shows in
GET /:id/logs, and the adherence figure (GET /adherence) moves.status: 'missed'/'skipped' it counts toward total but NOT taken, so
it lowers the adherence rate; it is never silently counted as taken (the
default status is taken, so a miss must be logged as a miss, not omitted).rate 0, total 0), never 100%; a
partial day (some doses logged, some not yet) is never shown as complete.GET /:id, PUT /:id,
DELETE /:id, or /:id/logs returns 404 (owner-scoped), never A's row; an
unauthenticated request 401s. Confirm PHI (name, dosage, notes) is never
written to server logs in the clear.