Skip to content

Oseni03/Notall

Repository files navigation

Notall

A multi-tenant SaaS notes application built with Next.js, demonstrating enterprise-grade multi-tenancy, authentication, and subscription management patterns.

Overview

Notall is a production-ready multi-tenant application where multiple organizations (tenants) can securely manage their users and notes with complete data isolation. Built as a Next.js SaaS boilerplate with role-based access control and subscription feature gating.


πŸš€ Features

Core Functionality

  • Multi-Tenancy - Strict tenant isolation using shared schema with organizationId filtering
  • Notes Management - Full CRUD operations with tenant-aware access control
  • Team Collaboration - User invitations, role management, and permissions
  • JWT Authentication - Secure token-based authentication with role-based authorization

SaaS Features

  • Subscription Tiers
    • Free Plan: 3 users, 50 notes limit
    • Pro Plan: Unlimited users and notes
  • Admin Controls - Invite users and upgrade subscriptions
  • Usage Tracking - Monitor notes and user limits per organization
  • API Access - RESTful API with tenant isolation

Advanced Enterprise Features (Upcoming)

  • Soft Deletes & Trash - Move notes to Trash for 30 days before permanent auto-purge
  • Activity Logs / Audit Trails - Detailed history of who edited what and when for every note
  • Version History - Full version snapshots with the ability to revert to any point in time

πŸ—οΈ Tech Stack

  • Framework: Next.js 14+ with App Router
  • Language: TypeScript
  • Database: PostgreSQL with Prisma ORM
  • Authentication: BetterAuth (JWT-based with OAuth support)
  • Payments: Polar.sh for subscription management
  • Email: Resend
  • UI: React + shadcn/ui + Tailwind CSS
  • State Management: Zustand
  • Forms: React Hook Form + Zod validation
  • Deployment: Vercel

πŸš€ Getting Started

Prerequisites

  • Node.js 18+
  • PostgreSQL database
  • npm or yarn

Installation

  1. Clone the repository:
git clone <repository-url>
cd notall
  1. Install dependencies:
npm install
  1. Set up environment variables:
cp .env.example .env

Configure your .env file:

BETTER_AUTH_SECRET=your_secret_key_here
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"

# Polar.sh Configuration
POLAR_ACCESS_TOKEN=your_polar_access_token_here
POLAR_WEBHOOK_SECRET=your_polar_webhook_secret_here
NEXT_PUBLIC_FREE_PLAN_ID=your_free_plan_id_here
NEXT_PUBLIC_PRO_PLAN_ID=your_pro_plan_id_here

# Email
RESEND_API_KEY=your_resend_api_key_here

# OAuth (Optional)
GOOGLE_CLIENT_ID=your_google_client_id_here
GOOGLE_CLIENT_SECRET=your_google_client_secret_here
  1. Generate Prisma client and push schema:
npx prisma generate
npx prisma db push
  1. Start the development server:
npm run dev

Visit http://localhost:3000 to access the application.


πŸ“‚ Project Structure

notall/
β”œβ”€β”€ prisma/
β”‚   └── schema.prisma         # Database schema
β”œβ”€β”€ public/                   # Static assets
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ api/              # API routes (thin wrappers)
β”‚   β”‚   β”‚   β”œβ”€β”€ accept-invitation/
β”‚   β”‚   β”‚   β”œβ”€β”€ notes/        # Notes CRUD endpoints
β”‚   β”‚   β”‚   β”œβ”€β”€ organizations/
β”‚   β”‚   β”‚   └── users/
β”‚   β”‚   β”œβ”€β”€ dashboard/        # Protected dashboard routes
β”‚   β”‚   β”‚   β”œβ”€β”€ settings/
β”‚   β”‚   β”‚   └── users/
β”‚   β”‚   β”œβ”€β”€ login/            # Login page
β”‚   β”‚   β”œβ”€β”€ signup/           # Signup page
β”‚   β”‚   β”œβ”€β”€ layout.tsx
β”‚   β”‚   └── page.tsx
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ emails/           # Email templates
β”‚   β”‚   β”œβ”€β”€ forms/            # Form components
β”‚   β”‚   β”œβ”€β”€ settings/         # Settings components
β”‚   β”‚   β”œβ”€β”€ theme/            # Theme components
β”‚   β”‚   β”œβ”€β”€ ui/               # shadcn/ui components
β”‚   β”‚   β”œβ”€β”€ app-sidebar.tsx
β”‚   β”‚   β”œβ”€β”€ nav-main.tsx
β”‚   β”‚   β”œβ”€β”€ nav-projects.tsx
β”‚   β”‚   β”œβ”€β”€ nav-user.tsx
β”‚   β”‚   └── team-switcher.tsx
β”‚   β”œβ”€β”€ hooks/                # Custom React hooks
β”‚   β”œβ”€β”€ lib/                  # Utility functions
β”‚   β”‚   β”œβ”€β”€ auth.ts           # BetterAuth configuration
β”‚   β”‚   └── utils.ts          # Helper functions
β”‚   β”œβ”€β”€ server/               # Business logic (single source of truth)
β”‚   β”‚   β”œβ”€β”€ notes.ts          # Notes operations
β”‚   β”‚   β”œβ”€β”€ organizations.ts  # Organization operations
β”‚   β”‚   └── users.ts          # User operations
β”‚   β”œβ”€β”€ types/                # TypeScript types
β”‚   └── zustand/              # State management
β”‚       └── providers/
β”œβ”€β”€ .env.example              # Environment variables template
β”œβ”€β”€ components.json           # shadcn/ui config
β”œβ”€β”€ eslint.config.mjs
β”œβ”€β”€ middleware.ts             # Next.js middleware
β”œβ”€β”€ next.config.ts
β”œβ”€β”€ next-env.d.ts
β”œβ”€β”€ package.json
└── tsconfig.json

πŸ” Key Implementation Details

1. Multi-Tenancy Architecture

Approach: Shared database, shared schema with tenant isolation via organizationId

All database queries are scoped to the authenticated user's organization:

// Example: Tenant-isolated query
const notes = await prisma.note.findMany({
	where: {
		organizationId: activeOrganization.id,
		userId: user.id,
	},
});

Schema Example:

model Note {
  id             String       @id @default(cuid())
  organizationId String
  userId         String
  title          String
  content        String
  createdAt      DateTime     @default(now())
  updatedAt      DateTime     @updatedAt

  organization   Organization @relation(fields: [organizationId], references: [id])
  user           User         @relation(fields: [userId], references: [id])
}

2. Authentication Flow

  1. User logs in via /api/auth/login
  2. JWT token generated and returned
  3. Token validated by middleware on protected routes
  4. User's organization context loaded for all requests

3. Subscription Feature Gating

Free Plan Limits:

  • 3 users per organization
  • 50 notes per organization

Pro Plan:

  • Unlimited users and notes
  • Only accessible to Admin users for subscription management

4. Notes API Endpoints

All endpoints enforce tenant isolation:

  • POST /api/notes - Create note
  • GET /api/notes - List all notes (tenant-scoped)
  • GET /api/notes/:id - Get single note
  • PUT /api/notes/:id - Update note
  • DELETE /api/notes/:id - Delete note

🎯 Development Guidelines

Code Organization Pattern

  1. Business Logic: Write all operations in src/server/ functions
  2. API Routes: Keep routes thin - just call server functions
  3. State Updates: Update Zustand stores after successful mutations
  4. Type Safety: Define interfaces in src/types/

Example Workflow

// 1. Define server function (src/server/notes.ts)
export async function createNote(data: CreateNoteInput) {
	// Business logic here
}

// 2. Call from API route (src/app/api/notes/route.ts)
export async function POST(request: Request) {
	const result = await createNote(data);
	return NextResponse.json(result);
}

// 3. Update state in component
const addNote = async (data) => {
	const note = await fetch("/api/notes", {
		method: "POST",
		body: JSON.stringify(data),
	});
	notesStore.addNote(note); // Update Zustand
};

Development Commands

# Development
npm run dev              # Start dev server
npm run lint             # Run ESLint
npm run build            # Production build

# Database
npx prisma generate      # Generate Prisma client
npx prisma db push       # Push schema changes
npx prisma studio        # Open database GUI

🌐 API Reference

Health Check

GET /api/health
Response: { "status": "ok" }

Authentication

POST /api/auth/login
Body: { "email": "admin@acme.test", "password": "password" }
Response: { "token": "jwt-token", "user": {...} }

Notes Operations

# Create Note
POST /api/notes
Headers: { "Authorization": "Bearer <token>" }
Body: { "title": "My Note", "content": "Note content" }

# List Notes
GET /api/notes
Headers: { "Authorization": "Bearer <token>" }

# Update Note
PUT /api/notes/:id
Headers: { "Authorization": "Bearer <token>" }
Body: { "title": "Updated Title", "content": "Updated content" }

# Delete Note
DELETE /api/notes/:id
Headers: { "Authorization": "Bearer <token>" }

🚒 Deployment

Vercel Deployment (Recommended)

  1. Push your code to GitHub
  2. Import project in Vercel
  3. Configure environment variables
  4. Deploy

Environment Variables for Production

BETTER_AUTH_SECRET=your_production_secret_here
BETTER_AUTH_URL=https://yourdomain.com
NEXT_PUBLIC_APP_URL=https://yourdomain.com
DATABASE_URL="postgresql://..."

POLAR_ACCESS_TOKEN=your_polar_access_token
POLAR_WEBHOOK_SECRET=your_polar_webhook_secret
NEXT_PUBLIC_FREE_PLAN_ID=your_free_plan_id
NEXT_PUBLIC_PRO_PLAN_ID=your_pro_plan_id

RESEND_API_KEY=your_resend_api_key

GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret

Database Setup

Ensure your production database is set up:

npx prisma db push

πŸ§ͺ Testing

The application includes comprehensive validation coverage:

Automated Test Coverage:

  • βœ… Health endpoint availability
  • βœ… Authentication flow
  • βœ… Tenant isolation enforcement
  • βœ… Role-based access restrictions
  • βœ… Subscription limits and upgrades
  • βœ… CRUD operations
  • βœ… Frontend accessibility

πŸ“‹ Features Checklist

  • Multi-tenant architecture with data isolation
  • JWT-based authentication
  • Role-based authorization (Admin/Member)
  • Free and Pro subscription tiers
  • Notes CRUD with tenant scoping
  • User invitation system
  • Subscription upgrade endpoint
  • Usage limits enforcement
  • Responsive frontend UI
  • API health monitoring
  • Production deployment on Vercel

🀝 Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Follow the code organization patterns in src/server/ for business logic
  4. Ensure all database queries include organizationId filtering
  5. Update Zustand stores after mutations
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

πŸ“„ License

MIT License - see LICENSE file for details


πŸ“ž Support

For issues and questions:

  • Open an issue on GitHub
  • Check the implementation details above
  • Review the code examples in src/server/

Built with Next.js as a SaaS boilerplate demonstrating multi-tenant architecture patterns

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors