Cut the guesswork. Discover which platforms actually bring customers to your African business.
Smartistics is a SaaS analytics and optimization platform that unifies ad data from Google Ads, Meta (Facebook/Instagram), LinkedIn, TikTok (and more), then calculates ROI/ROAS, recommends budget reallocation, and automates reporting. It’s built for African markets (mobile‑first, multi‑currency, bandwidth‑friendly) and ships with n8n workflows for self‑sustaining data syncs, alerts, billing, and backups.
- Features
- Architecture
- Tech Stack
- Monorepo Structure
- Prerequisites
- Quick Start (Docker)
- Manual Setup (Dev)
- Environment Variables
- n8n Automation Workflows
- Data Model (Essentials)
- Using Animated Icons & Video
- Development Tips
- Security & Compliance
- Deploying to Production
- Roadmap
- License
- Unified dashboards: Spend, revenue, ROAS, CAC, impressions, clicks, conversions per platform & campaign.
- AI‑assisted optimization: Budget reallocation recommendations with projected impact.
- Attribution models: First/last touch, linear; easy to extend to data‑driven.
- Automated reporting: Weekly/monthly PDFs or slides sent to Slack/Email.
- Alerts: CPA/ROAS thresholds, anomalies, channel degradation.
- Multi‑currency: ZAR/NGN/KES/GHS + base currency conversions.
- Bandwidth‑friendly:
.webmhero video, animated SVG/Lottie icons.
+-------------------+ +------------------+ +-------------------+
| Frontend (Next) | <----> | API (Node/Nest) | <----> | PostgreSQL (DB) |
| React + TS | | REST/GraphQL | | Prisma/TypeORM |
+---------^---------+ +--------^---------+ +---------^---------+
| | |
| | |
v v v
Lottie/SVG n8n Workflows Redis (cache)
(icons/animations) (ingest, clean, compute, alerts, billing, backups)
- Frontend: Next.js (App Router) + React + TypeScript, TailwindCSS, Recharts
- Backend: Node.js (NestJS), TypeScript, Prisma ORM
- DB/Cache: PostgreSQL, Redis
- Automation: n8n (Docker)
- Auth: NextAuth (OAuth/JWT) or custom OAuth2 for ad APIs
- Payments: Mpesa Billing for local & Stripe for global payments
- Observability: Sentry (errors), Prometheus + Grafana (metrics) & Microsoft Clarity
smartistics/
├─ apps/
│ ├─ web/ # Next.js (client + server components)
│ └─ api/ # NestJS (or Express) REST/GraphQL API
├─ packages/
│ ├─ ui/ # Shared UI components (TS, Tailwind)
│ └─ config/ # ESLint, TS configs, env loaders
├─ infra/
│ ├─ docker/ # docker-compose, Dockerfiles
│ └─ n8n/ # workflow JSONs (ingest/report/alerts)
└─ prisma/ # schema.prisma, migrations, seed
- Node.js ≥ 18, pnpm or npm
- Docker + Docker Compose (recommended for quick start)
- Accounts & API credentials for: Google Ads, Meta, LinkedIn, TikTok
- Stripe account (for subscriptions)
One command to spin up web, api, db (Postgres), redis, and n8n.
- Clone & enter
git clone https://github.com/your-org/smartistics.git
cd smartistics- Create env files (copy examples below into the respective paths)
.env
apps/web/.env.local
apps/api/.env
- Run
docker compose -f infra/docker/docker-compose.yml up --build- Open
- Web (Next.js): http://localhost:3000
- API (Nest/Express): http://localhost:4000
- n8n: http://localhost:5678
- Postgres: localhost:5432 (user/pass per .env)
version: "3.9"
services:
db:
image: postgres:15
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
ports: ["5432:5432"]
volumes:
- db_data:/var/lib/postgresql/data
redis:
image: redis:7
ports: ["6379:6379"]
api:
build: ../../apps/api
env_file: ../../apps/api/.env
depends_on: [db, redis]
ports: ["4000:4000"]
web:
build: ../../apps/web
env_file: ../../apps/web/.env.local
depends_on: [api]
ports: ["3000:3000"]
n8n:
image: n8nio/n8n:latest
env_file: ../../.env
ports: ["5678:5678"]
volumes:
- n8n_data:/home/node/.n8n
depends_on: [api, db]
volumes:
db_data:
n8n_data:pnpm install- Postgres (Docker):
docker run -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=smartistics -d postgres:15 - Redis (Docker):
docker run -p 6379:6379 -d redis:7
cd apps/api
pnpm prisma migrate dev
pnpm prisma db seed
pnpm devAPI runs on http://localhost:4000.
cd apps/web
pnpm devWeb runs on http://localhost:3000.
Next.js App Router Note: Any component using hooks must start with the directive:
"use client";Create these files:
# Postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=smartistics
DATABASE_URL=postgresql://postgres:postgres@db:5432/smartistics
# Redis
REDIS_URL=redis://redis:6379
# n8n
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=change-me
N8N_ENCRYPTION_KEY=generate_a_long_random_key
# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
PORT=4000
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/smartistics
REDIS_URL=redis://localhost:6379
JWT_SECRET=replace-with-strong-secret
# Ad APIs (placeholders)
GOOGLE_ADS_CLIENT_ID=...
GOOGLE_ADS_CLIENT_SECRET=...
GOOGLE_ADS_REFRESH_TOKEN=...
META_APP_ID=...
META_APP_SECRET=...
LINKEDIN_CLIENT_ID=...
LINKEDIN_CLIENT_SECRET=...
TIKTOK_CLIENT_ID=...
TIKTOK_CLIENT_SECRET=...
NEXT_PUBLIC_API_URL=http://localhost:4000
NEXTAUTH_SECRET=replace-with-strong-secret
NEXTAUTH_URL=http://localhost:3000
STRIPE_PUBLIC_KEY=pk_test_...
Deploy n8n (Docker) then log in at http://localhost:5678.
-
Cron → HTTP Request (Google Ads) → Function (normalize) → Postgres (insert) → Repeat for Meta/LinkedIn/TikTok
-
Schema normalization target:
{ "platform": "google_ads", "campaign_id": "123", "date": "2025-08-28", "spend": 2100.50, "impressions": 67000, "clicks": 1340, "conversions": 94, "revenue": 7350.00, "currency": "ZAR" }
- Cron (hourly) → Postgres (aggregate last 7/30d) → Function (compute ROAS, CAC; run simple mean‑variance/heuristic budget shift) → Postgres (write recommendations) → Slack (summary)
- Cron (every 30m) → Postgres (threshold queries) → IF (ROAS < target or CPA > target) → Slack/Email → Webhook (notify API for banner alerts)
- Cron (weekly) → Postgres (aggregate) → Function (HTML → PDF) → Email (attach PDF) or Slack (upload file)
- Stripe Trigger (webhook) → n8n Webhook → Function (map event) → Postgres (update subscription) → Email (receipt)
- Cron (daily) → Execute Command (
pg_dump) → S3 Upload → Slack (backup status)
You can export/import these as JSON within n8n for version control (store in
infra/n8n/).
Prisma excerpt (prisma/schema.prisma):
model PlatformAccount {
id String @id @default(cuid())
userId String
provider String // google_ads | meta | linkedin | tiktok
accessToken String?
refreshToken String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model MetricDaily {
id String @id @default(cuid())
date DateTime
platform String
campaignId String
spend Float
impressions Int
clicks Int
conversions Int
revenue Float
currency String @default("ZAR")
}
model Recommendation {
id String @id @default(cuid())
date DateTime @default(now())
platform String
currentShare Float // % of total budget
suggestedShare Float
projectedImpact Float // +/‑ currency units
}-
Install:
pnpm add lottie-react -
Use:
"use client"; import { Player } from "@lottiefiles/react-lottie-player"; export default function HeroIcons() { return ( <Player src="/animations/analytics.json" autoplay loop style={{ height: 80 }} /> ); }
Sources: LottieFiles (free/paid), Lordicon (animated packs).
<video
className="w-full h-auto"
autoPlay
loop
muted
playsInline
poster="/video/poster.jpg"
>
<source src="/video/hero.webm" type="video/webm" />
<source src="/video/hero.mp4" type="video/mp4" />
</video>- App Router & Hooks: Client components must start with
"use client";. - Type‑safety: End‑to‑end types with TypeScript + Zod (request validation).
- Testing: Vitest/Jest + Playwright for E2E.
- Seeding:
pnpm prisma db seedpopulates demo campaigns & metrics.
- Use HTTPS everywhere (Let’s Encrypt/ACM).
- Store secrets in Doppler/Vault/Parameter Store (never in git).
- Principle of least privilege for ad APIs and DB.
- GDPR/POPIA: data export/delete endpoints, DPA with vendors, region pinning.
- Frontend: Vercel
- API: Fly.io/Render/AWS ECS
- DB: Neon/Supabase (managed Postgres)
- n8n: Docker on a small VM (e.g., Lightsail/Droplet) with HTTPS & basic auth
- Observability: Sentry DSN + Grafana dashboard
- Custom domain + SSL
- Env vars set in all platforms
- DB migrations applied
- Stripe webhooks configured to n8n → API
- Cron schedules (n8n) enabled
- Data‑driven attribution (Shapley/Markov)
- MMM (lightweight Bayesian model for channel contribution)
- Auto‑pause/boost budgets by rule
- Partner integrations (HubSpot/Salesforce, GA4)
Choose your license (MIT/Apache‑2.0/Proprietary). Update LICENSE accordingly.
This project uses a mock API during development, served from db.json via json-server. The app never imports db.json directly; data is fetched over HTTP to simulate a real backend.
npm install
npm run json-serverThis serves db.json at http://localhost:3001.
In a separate terminal, start the app:
npm run devGET http://localhost:3001/usersGET http://localhost:3001/subscriptionScenariosGET http://localhost:3001/charts?id=<line|bar|pie|area|scatter|composed>
- The UI remains stable with loading/error states.
- Auth falls back to unauthenticated; subscription data becomes
null. - Charts display an inline error within the chart card.
This mirrors a real API integration path; switching to production later is as simple as swapping the base URL or routing calls through your service layer.