Skip to content

Repository files navigation

Nudge

Your smart schedule & habit companion — never miss what matters.

Nudge is a full-stack reminder platform that keeps you on track with recurring events, color-coded categories, powerful search, a habit tracker with streaks, and real-time push notifications — all wrapped in a clean, animated Flutter experience.


Flutter Dart Node.js Express TypeScript Prisma PostgreSQL Firebase Redis Turborepo


Table of Contents


Features

Recurring Events

Create events that repeat daily, weekly, monthly, or yearly. Nudge auto-generates future instances, keeps them topped up with a daily background job, and cascades deletes across a whole series. Editing/deleting a repeating event asks whether you mean this event only or the whole series.

Categories & Tags

Organize events with color-coded categories. Create, edit, and manage them, assign multiple to any event, and see them as colorful badges across the calendar and detail screens.

Search & Filtering

Instantly search events by title and filter by status (Active / Completed / Cancelled), event type, and category — all with a smooth, debounced search experience.

Habit Tracker

Build daily or weekly habits, check in with one tap, and watch your streak grow. Habits appear both as a dedicated screen and as a quick "Today's Habits" section on the dashboard, with optimistic UI updates.

Real Push Notifications

Powered by Firebase Cloud Messaging. A resilient scheduler dispatches reminders with row-locking (SKIP LOCKED), exponential-backoff retries, and stale-lock recovery. Tapping a notification deep-links straight to the event. Runs in a console simulator mode during local dev.

Authentication

Email/password and Google Sign-In (OAuth) with JWT access + refresh token rotation, session management, and secure password hashing.

Google Calendar Sync

Connect a Google account (even a different one from your login) to sync events to Google Calendar.


Tech Stack

Frontend (Mobile)

  • Flutter — cross-platform UI
  • Dart — language
  • Firebase Messaging — push notifications
  • google_sign_in — OAuth
  • google_fonts (Sora / Inter), iconsax — icons
  • flutter_animate — micro-animations
  • http + shared_preferences — networking & local storage

Backend (API)

  • Node.js + Express — REST API
  • TypeScript — type safety
  • Prisma ORM — database access
  • PostgreSQL (Neon) — database
  • Redis — rate limiting
  • Firebase Admin — FCM dispatch
  • Zod — validation · Jose — JWT · node-cron — scheduler

Tooling

   

Turborepo monorepo · Vitest tests · ESLint + Prettier · npm workspaces


Monorepo Structure

nudge/
├── apps/
│   ├── backend/        # Express + TypeScript REST API
│   │   └── src/
│   │       ├── modules/        # auth, events, reminders, categories,
│   │       │                   # habits, notifications, dashboard
│   │       ├── scheduler/      # cron: reminder dispatch + recurrence
│   │       ├── integrations/   # firebase (FCM)
│   │       └── config/         # env, logger
│   └── user-app/       # Flutter mobile client
│       └── lib/
│           ├── screens/        # dashboard, calendar, habits, profile…
│           └── services/       # api client + FCM service
└── packages/
    ├── db/             # Prisma schema, migrations, generated client
    ├── ui/             # shared UI package
    ├── eslint-config/  # shared lint config
    └── typescript-config/

Architecture

┌─────────────────────┐        HTTPS/REST        ┌──────────────────────┐
│   Flutter App        │ ───────────────────────► │   Express API         │
│  (Android / iOS)     │ ◄─────────────────────── │  (Node + TypeScript)  │
└─────────┬───────────┘        JWT auth            └──────────┬───────────┘
          │                                                    │
          │ FCM push                                Prisma ORM │
          ▼                                                    ▼
┌─────────────────────┐                            ┌──────────────────────┐
│  Firebase Cloud      │ ◄───── Firebase Admin ──── │   PostgreSQL (Neon)   │
│  Messaging           │                            │   + Redis (limits)    │
└─────────────────────┘                            └──────────────────────┘
                          ▲
                          │ node-cron every minute
                 ┌────────┴─────────┐
                 │  Reminder         │  claims due reminders (SKIP LOCKED),
                 │  Scheduler        │  dispatches FCM, retries w/ backoff,
                 └───────────────────┘  generates recurring instances daily

Getting Started

Prerequisites

  • Node.js >= 18 and npm >= 10
  • Flutter SDK >= 3.0
  • A PostgreSQL database (e.g. free Neon)
  • A Firebase project (for push notifications)

1. Clone & install

git clone <your-repo-url> nudge
cd nudge
npm install

2. Configure environment

Create packages/db/.env:

DATABASE_URL="postgresql://user:pass@host/dbname"

Create apps/backend/.env:

PORT=3000
DATABASE_URL="postgresql://user:pass@host/dbname"
CORS_ALLOWED_ORIGINS="*"

# JWT (RS256 keys)
JWT_ACCESS_PRIVATE_KEY="..."
JWT_ACCESS_PUBLIC_KEY="..."

# Google OAuth
GOOGLE_WEB_CLIENT_ID="xxx.apps.googleusercontent.com"

# Firebase Admin (from service-account JSON)
FIREBASE_PROJECT_ID="..."
FIREBASE_CLIENT_EMAIL="..."
FIREBASE_PRIVATE_KEY="..."

# Optional
REDIS_URL="redis://localhost:6379"

3. Set up the database

cd packages/db
npx prisma migrate deploy   # apply migrations
npx prisma generate         # generate the client

4. Run the backend

cd apps/backend
npm run dev        # http://localhost:3000  (listens on 0.0.0.0)

Health check: GET /health/live -> { "status": "alive" }


Running the Mobile App

Push notifications and Google Sign-In only work on real Android/iOS devices, not on Windows/desktop.

  1. Drop your Firebase google-services.json into apps/user-app/android/app/.
  2. Set your PC's LAN IP in apps/user-app/lib/services/api/api_client.dart:
    static String baseUrl = 'http://<YOUR_PC_LAN_IP>:3000/api/v1';
    (Find it with ipconfig -> "Wireless LAN adapter Wi-Fi" IPv4.)
  3. Connect your phone (USB debugging on), same Wi-Fi as your PC.
  4. Run:
    cd apps/user-app
    flutter pub get
    flutter run

Connectivity test: open http://<YOUR_PC_IP>:3000/health/live in your phone's browser. If you see {"status":"alive"}, you're connected.


API Overview

Base path: /api/v1

Area Endpoints
Auth POST /auth/register · POST /auth/login · POST /auth/google · POST /auth/refresh · GET /auth/me · GET/DELETE /auth/sessions
Events POST /events · GET /events (search, filters, pagination) · GET /events/:id · PATCH /events/:id · DELETE /events/:id · POST /events/:id/complete
Categories POST /categories · GET /categories · PATCH /categories/:id · DELETE /categories/:id
Habits POST /habits · GET /habits · PATCH /habits/:id · DELETE /habits/:id · POST/DELETE /habits/:id/check-in · GET /habits/:id/streak
Reminders POST /events/:id/reminders · GET /events/:id/reminders · PATCH /reminders/:id · DELETE /reminders/:id
Notifications PUT /notifications/device-token · GET /notifications/history · GET /notifications/health
Dashboard GET /dashboard/summary · GET /dashboard/analytics
Health GET /health/live · GET /health/ready · GET /health/scheduler

Database Schema

Core models (Prisma + PostgreSQL):

  • User — accounts, auth, timezone
  • Event — with recurrence fields (recurrenceFrequency, recurrenceInterval, recurrenceEndAt, parentEventId self-relation)
  • Reminder — scheduled offsets, retry state, locking
  • Category / EventCategory — categories + many-to-many join
  • Habit / HabitLog — habits + daily check-ins (unique per day)
  • DeviceToken / NotificationLog — FCM tokens + delivery audit
  • Session / AuthAccount — refresh tokens + OAuth links

How Notifications Work

  1. When you create an event with reminders, Reminder rows are scheduled at your chosen offsets.
  2. A node-cron job runs every minute and:
    • Recovers stale locks
    • Claims due reminders using SELECT ... FOR UPDATE SKIP LOCKED (safe for concurrency)
    • Dispatches them via Firebase Admin to all active device tokens
    • Retries failures with exponential backoff; deactivates dead tokens
  3. A daily job tops up open-ended recurring series ~30 days ahead.
  4. On device: background/closed -> shown in the system tray automatically; foreground -> a themed in-app banner; tapping -> deep-links to the event.

Without Firebase credentials, the backend logs a mock push to the console (simulator mode) — perfect for local dev.


Quality & Verification

Check Result
flutter analyze 0 issues
Backend tsc --noEmit 0 errors
Android debug APK build Builds successfully
Prisma migrate status In sync, no drift
# Frontend
cd apps/user-app && flutter analyze

# Backend
cd apps/backend && npx tsc --noEmit && npm test

Troubleshooting

App loads but no data (Connection timed out)
  • Your baseUrl must be your PC's LAN IP, not 127.0.0.1 (that means the phone itself).
  • Allow port 3000 through Windows Firewall (Admin PowerShell):
    New-NetFirewallRule -DisplayName "Nudge Backend 3000" -Direction Inbound -LocalPort 3000 -Protocol TCP -Action Allow
  • Use a normal Wi-Fi router, not a mobile hotspot (hotspots often block phone<->PC).
  • Test in the phone browser: http://<YOUR_PC_IP>:3000/health/live.
Google login shows a "test user" instead of my Gmail
  • Real Google Sign-In only works on a physical device (not Windows/emulator).
  • Ensure your debug SHA-1 is registered in Firebase and google-services.json is up to date.
  • If your OAuth consent screen is in Testing mode, add your Gmail under Test users.
Android build fails
  • Make sure google-services.json is in apps/user-app/android/app/.
  • Cleartext HTTP is enabled for LAN dev via usesCleartextTraffic="true" in the manifest.
  • Run flutter clean && flutter pub get and rebuild.

Built using Flutter & Node.js

About

Your smart schedule and habit companion. A full-stack monorepo featuring recurring events, color-coded categories, a habit tracker with streaks, and real-time push notifications. Built with Flutter, Node.js/Express, TypeScript, Prisma, PostgreSQL, and Redis.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages