Event management demo showcasing Ethiopian calendar integration with Drizzle ORM.
- ✅ Generated columns with Drizzle schema
- ✅ Ethiopian holidays with Amharic names
- ✅ Appointment scheduling
- ✅ Invoice management with Ethiopian due dates
- ✅ Input dates in Ethiopian format
- ✅ Ethiopian-only dates (no Gregorian storage)
- Node.js >= 16.0.0
- PostgreSQL >= 12
# Install dependencies
npm install
# Setup environment
cp env.example .env
# Edit .env with your DATABASE_URL
# Run migrations
npm run db:push
# Or: psql $DATABASE_URL -f drizzle/0000_init.sql
# Start server
npm run dev| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | (required) |
PORT |
Server port | 3002 |
curl -X POST http://localhost:3002/holidays/seedResponse:
{
"holidays": [
{
"id": 1,
"name": "Ethiopian New Year",
"nameAmharic": "እንቁጣጣሽ",
"holidayDate": "2025-09-11T00:00:00.000Z",
"holidayDateEthiopian": "2018-01-01T00:00:00.000Z"
}
]
}curl -X POST http://localhost:3002/appointments \
-H "Content-Type: application/json" \
-d '{
"title": "Business Meeting",
"clientName": "Dawit Haile",
"clientPhone": "+251911234567",
"ethiopianDate": "2018-05-15",
"duration": 90,
"notes": "Discuss partnership"
}'curl -X POST http://localhost:3002/invoices \
-H "Content-Type: application/json" \
-d '{
"clientName": "ABC Company",
"amount": "15000.00",
"ethiopianDueDate": "2018-05-30"
}'curl -X PATCH http://localhost:3002/invoices/1/payResponse shows both payment timestamps:
{
"id": 1,
"status": "paid",
"paidAt": "2026-01-04T12:00:00.000Z",
"paidAtEthiopian": "2018-04-26T12:00:00.000Z"
}The notes table demonstrates storing only Ethiopian timestamps - no Gregorian storage at all:
# Create a note - created_at defaults to current Ethiopian timestamp
curl -X POST http://localhost:3002/notes \
-H "Content-Type: application/json" \
-d '{"title": "Meeting Notes", "content": "Discussed Q1 goals"}'Response:
{
"id": 1,
"title": "Meeting Notes",
"content": "Discussed Q1 goals",
"category": "general",
"createdAt": "2018-04-26T14:30:00.000Z" // Ethiopian timestamp only!
}# Query notes by Ethiopian date
curl http://localhost:3002/notes/by-date/2018-04-26export const notes = pgTable("notes", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
content: text("content"),
// Ethiopian timestamp only - defaults to current Ethiopian timestamp
createdAt: timestamp("created_at").default(sql`to_ethiopian_timestamp()`).notNull(),
});CREATE TABLE "notes" (
"id" SERIAL PRIMARY KEY,
"title" TEXT NOT NULL,
"content" TEXT,
"created_at" TIMESTAMP NOT NULL DEFAULT to_ethiopian_timestamp()
);import { pgTable, timestamp } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";
// Option 1: Both Gregorian and Ethiopian (generated column)
export const invoices = pgTable("invoices", {
dueDate: timestamp("due_date").notNull(),
dueDateEthiopian: timestamp("due_date_ethiopian").generatedAlwaysAs(
sql`to_ethiopian_timestamp(due_date)`
),
});
// Option 2: Ethiopian only (no Gregorian storage)
export const notes = pgTable("notes", {
createdAt: timestamp("created_at").default(sql`to_ethiopian_timestamp()`).notNull(),
});drizzle/
└── 0000_init.sql # Functions + Tables
src/
├── schema.ts # Drizzle schema
├── db.ts # Database connection
└── server.ts # Express API