SuperSaaS Integration
@friendlyinternet/nuxt-crouton-supersaas provides production-ready SuperSaaS integration.The @friendlyinternet/nuxt-crouton-supersaas package provides pre-built connectors for integrating SuperSaaS auth systems with Nuxt Crouton, app-level i18n locale files, and utilities for team-based applications.
Package Overview
Package: @friendlyinternet/nuxt-crouton-supersaasVersion: 1.0.0
Type: SuperSaaS Integration Layer
What It Does
This package serves two main purposes:
- External Collection Connectors - When using Crouton's
CroutonReferenceSelectwith external collections (like users from your auth system), this package provides ready-to-use implementations so you can configure instead of writing from scratch. - App-Level i18n Locale Files - Ships with pre-defined translation strings for common app functionality (auth, navigation, teams, errors) that merge with your layer translations.
Installation
pnpm add @friendlyinternet/nuxt-crouton-supersaas
i18n Locale Files
The package includes app-level translation strings that serve as defaults for common UI elements.
Included Translations
// Example: en.json
{
"common": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"create": "Create",
"loading": "Loading...",
"noResults": "No results found",
"confirmDelete": "Are you sure you want to delete this?"
},
"auth": {
"signIn": "Sign In",
"signOut": "Sign Out",
"signUp": "Sign Up",
"forgotPassword": "Forgot Password?",
"resetPassword": "Reset Password",
"email": "Email",
"password": "Password"
},
"navigation": {
"dashboard": "Dashboard",
"settings": "Settings",
"profile": "Profile",
"teams": "Teams",
"admin": "Admin"
},
"teams": {
"members": "Members",
"invite": "Invite Member",
"role": "Role",
"owner": "Owner",
"admin": "Admin",
"member": "Member"
},
"errors": {
"generic": "Something went wrong",
"notFound": "Not found",
"unauthorized": "Unauthorized",
"forbidden": "Forbidden"
}
}
Supported Locales
- English (en)
- Dutch (nl)
- French (fr)
Merge Order
Translations merge in this order (last wins):
1. nuxt-crouton/i18n/locales/ (Crouton component strings)
2. nuxt-crouton-supersaas/i18n/locales/ (App strings - this package)
3. layers/[domain]/i18n/locales/ (Domain-specific strings)
4. app/i18n/locales/ (App-level overrides)
SuperSaaS Connector
The primary connector for team-based user management systems.
Usage Patterns
The connector package supports two usage patterns depending on your needs.
Pattern 1: Proxy Mode (Recommended)
Best for: Most use cases - connects to existing endpoints without file copying.
Proxy mode forwards requests to your existing API endpoints and transforms responses to Crouton format. No duplicate endpoints, single source of truth.
Benefits:
- No file copying or maintenance
- No duplicate endpoints
- Single source of truth (your existing endpoints)
- Easy to update and customize
Configuration:
// app.config.ts
import { connectSupersaas } from '@friendlyinternet/nuxt-crouton-supersaas/supersaas'
import { z } from 'zod'
const userSchema = z.object({
id: z.string(),
title: z.string(),
email: z.string().optional(),
avatarUrl: z.string().optional(),
role: z.string().optional()
})
export default defineAppConfig({
croutonCollections: {
users: connectSupersaas({
sourceEndpoint: 'members', // Uses your existing /api/teams/[id]/members
schema: userSchema,
transform: (member) => ({
id: member.userId,
title: member.name,
email: member.email,
avatarUrl: member.avatarUrl,
role: member.role
})
})
}
})
Pattern 2: Copy-Paste Mode
Best for: Heavy customization - when you need full control over endpoint logic.
Copy connector files into your project for maximum flexibility and customization.
# Install package
pnpm add @friendlyinternet/nuxt-crouton-supersaas
# Copy connector files to your project
cp -r node_modules/@friendlyinternet/nuxt-crouton-supersaas/connectors/supersaas/app/composables/useUsers.ts ./app/composables/
cp -r node_modules/@friendlyinternet/nuxt-crouton-supersaas/connectors/supersaas/server/* ./server/
Then register in app.config.ts:
import { usersConfig } from './composables/useUsers'
export default defineAppConfig({
croutonCollections: {
users: usersConfig
}
})
Requirements
Your SuperSaaS project must have:
Required for All Operations:
- Team-based routing:
/api/teams/[id]/* - validateTeamOwnership() function for auth:
// Expected in: server/utils/teamValidation.ts (or similar) export async function validateTeamOwnership(event: H3Event, teamId: string) { // Throws error if user doesn't have access }
Required for Read Operations (List, View): 3. getActiveTeamMembers() function that returns team members:
// Expected in: server/database/queries/teams.ts (or similar)
export async function getActiveTeamMembers(teamId: string) {
// Returns array of team members
}
Optional for Update/Delete Operations: 4. updateTeamMember() - For member updates 5. deleteTeamMember() - For member deletion
Quick Setup (Proxy Mode)
Step 1: Install Package
pnpm add @friendlyinternet/nuxt-crouton-supersaas
Step 2: Configure in app.config.ts
import { connectSupersaas } from '@friendlyinternet/nuxt-crouton-supersaas/supersaas'
import { z } from 'zod'
const userSchema = z.object({
id: z.string(),
title: z.string(),
email: z.string().optional(),
avatarUrl: z.string().optional(),
role: z.string().optional()
})
export default defineAppConfig({
croutonCollections: {
users: connectSupersaas({
sourceEndpoint: 'members', // Your existing /api/teams/[id]/members endpoint
schema: userSchema,
transform: (member) => ({
id: member.userId,
title: member.name,
email: member.email,
avatarUrl: member.avatarUrl,
role: member.role
})
})
}
})
Done!
Your forms with collection="users" will now work:
<CroutonReferenceSelect collection="users" v-model="state.updatedBy" />
How it works: The connector proxies requests to your existing SuperSaaS /api/teams/[id]/members endpoint and transforms the response to Crouton format.
What Gets Created (Copy-Paste Mode)
After copying, you'll have these files:
your-project/
├── app/
│ └── composables/
│ └── useUsers.ts # Collection config
└── server/
└── api/
└── teams/
└── [id]/
└── members/
├── index.get.ts # List all members (required)
├── [memberId].get.ts # Get single member (required)
├── [memberId].patch.ts # Update member (optional)
└── [memberId].delete.ts # Delete member (optional)
Customization Examples
Add Custom Fields
Edit app/composables/useUsers.ts to extend the schema with custom fields:
const userSchema = z.object({
// ... base fields (id, title, email, avatarUrl, role)
// Add your custom fields:
department: z.string().optional(),
phoneNumber: z.string().optional()
})
Customize Display Format
Edit the transform function to customize how users appear in dropdowns:
transform: (member) => ({
id: member.userId,
title: `${member.name} (${member.role})`, // Show role in dropdown
email: member.email,
avatarUrl: member.avatarUrl,
role: member.role,
department: member.department
})
Filter Users
Edit the fetch function in server/api/teams/[id]/users/index.get.ts:
async (event) => {
const teamId = getRouterParam(event, 'id')
await validateTeamOwnership(event, teamId!)
const members = await getActiveTeamMembers(teamId!)
// Filter to only show admins and owners:
return members.filter(m => ['admin', 'owner'].includes(m.role))
}
Auto-Populate Pattern (Recommended)
Users shouldn't manually select themselves. Auto-populate in your API handlers:
// Example: server/api/teams/[id]/bookings-bookings/[itemId].patch.ts
export default defineEventHandler(async (event) => {
const session = await getUserSession(event)
const body = await readBody(event)
// Auto-populate with current user
body.updatedBy = session.user.id
// ... rest of your update logic
})
Then remove the updatedBy field from your forms since users don't need to see it.
Helper Functions
The package provides two core helper functions for creating connectors.
defineExternalCollection
Creates a collection configuration for external resources.
Signature:
function defineExternalCollection(config: ExternalCollectionConfig): CollectionConfig
Parameters:
interface ExternalCollectionConfig {
name: string // Collection name (must match app.config.ts key)
schema: z.ZodSchema // Zod schema for validation
apiPath?: string // API path (defaults to collection name)
fetchStrategy?: 'query' | 'restful' // Default: 'query'
readonly?: boolean // Default: true (read-only)
meta?: {
label?: string
description?: string
}
proxy?: {
enabled: boolean
sourceEndpoint: string
transform: (item: any) => { id: string; title: string; [key: string]: any }
}
}
Example:
import { defineExternalCollection } from '@friendlyinternet/nuxt-crouton/app/composables/useExternalCollection'
import { z } from 'zod'
const userSchema = z.object({
id: z.string(),
title: z.string(), // Required for CroutonReferenceSelect display
email: z.string().optional(),
avatarUrl: z.string().optional()
})
export const usersConfig = defineExternalCollection({
name: 'users',
schema: userSchema,
readonly: true,
meta: {
label: 'Users',
description: 'Team members from auth system'
}
})
createExternalCollectionHandler
Creates API handlers for external collections with automatic transformation.
Signature:
function createExternalCollectionHandler<T>(
fetchFn: (event: H3Event) => Promise<T[]> | T[],
transform: (item: T) => { id: string; title: string; [key: string]: any }
): EventHandler
Example:
// server/api/teams/[id]/users/index.get.ts
import { createExternalCollectionHandler } from '@friendlyinternet/nuxt-crouton'
export default createExternalCollectionHandler(
async (event) => {
const teamId = getRouterParam(event, 'id')
await validateTeamOwnership(event, teamId!)
return await getActiveTeamMembers(teamId!)
},
(member) => ({
id: member.userId,
title: member.name, // Required for dropdown display
email: member.email,
avatarUrl: member.avatarUrl,
role: member.role
})
)
Features:
- Automatic error handling
- Support for
?ids=query parameter (for single item fetches) - Consistent response format
- Type-safe transformations
Data Flow
Understanding how connector data flows through your application:
1. User opens form
↓
2. <CroutonReferenceSelect collection="users" />
↓
3. Looks up "users" in app.config.ts → Found!
↓
4. GET /api/teams/{teamId}/users
↓
5. validateTeamOwnership() checks access
↓
6. getActiveTeamMembers() fetches members
↓
7. Transform to Crouton format: { id, title, ... }
↓
8. Dropdown shows: "Alice", "Bob", etc.
↓
9. User selects "Alice"
↓
10. Form saves: updatedBy: "alice-user-id"
Troubleshooting
Error: "Collection 'users' not registered"
Make sure you registered the collection in app.config.ts:
import { usersConfig } from './composables/useUsers'
export default defineAppConfig({
croutonCollections: {
users: usersConfig // Must be present
}
})
Error: "getActiveTeamMembers is not defined"
The API endpoint expects this function. Update the import to match your project structure:
// Change this line to match your project:
// import { getActiveTeamMembers } from '@@/server/database/queries/teams'
import { getActiveTeamMembers } from '~/path/to/your/function'
Error: "validateTeamOwnership is not defined"
Update the import to match your project:
// import { validateTeamOwnership } from '@@/server/utils/teamValidation'
import { validateTeamOwnership } from '~/path/to/your/function'
Users Dropdown is Empty
Check your API endpoint is returning data:
# Test the endpoint
curl http://localhost:3000/api/teams/YOUR_TEAM_ID/users
Should return:
[
{ "id": "user1", "title": "Alice", "email": "alice@example.com" },
{ "id": "user2", "title": "Bob", "email": "bob@example.com" }
]
If empty, verify:
getActiveTeamMembers()is returning datavalidateTeamOwnership()is not blocking access- Transform function is correct
Creating Custom Connectors
You can create connectors for other auth systems by following the SuperSaaS pattern.
Connector Structure
connectors/
└── your-system/
├── README.md # Usage documentation
├── index.ts # Export + factory function
├── app/
│ └── composables/
│ └── useUsers.ts # Collection config
└── server/
└── api/
└── [your-routes]/
└── users/
├── index.get.ts # List endpoint
└── [userId].get.ts # Single item endpoint
Implementation Checklist
- Collection Config (
useUsers.ts):- Define Zod schema with required
idandtitlefields - Use
defineExternalCollection()helper - Set
readonly: trueif users shouldn't edit via Crouton
- Define Zod schema with required
- API Endpoints:
- Implement list endpoint (required)
- Implement single item endpoint (required)
- Use
createExternalCollectionHandler()for consistent responses - Add proper error handling
- Transform Function:
- Convert external format to
{ id, title, ...other }format - Ensure
titleis user-friendly (shown in dropdowns) - Include all fields needed by forms
- Convert external format to
- Factory Function (for proxy mode):
- Accept configuration object
- Return
defineExternalCollection()result - Include transform logic
- Documentation:
- Requirements section (what functions/endpoints are needed)
- Quick setup guide
- Customization examples
- Troubleshooting section
Architecture
This package follows the same addon pattern as other Crouton addons:
@friendlyinternet/nuxt-crouton ← Core (provides utilities)
@friendlyinternet/nuxt-crouton-assets ← Asset management addon
@friendlyinternet/nuxt-crouton-i18n ← i18n addon
@friendlyinternet/nuxt-crouton-supersaas ← SuperSaaS integration (this package)
Design Principles
- Single Source of Truth: Connectors proxy to existing endpoints, not duplicate them
- Minimal Configuration: Simple config in
app.config.ts, no complex setup - Type-Safe: Full TypeScript support with Zod schemas
- Framework Agnostic: Works with any auth system that has HTTP endpoints
- Composable: Mix and match with other Crouton features
Benefits
- No Duplicate User Management - External system remains source of truth
- Team-Scoped - Users only see their team members (SuperSaaS)
- Secure - Reuses existing auth validation
- Type-Safe - Full TypeScript support with Zod
- Customizable - Easy to modify for your needs
- No Lock-In - Copy-paste mode for full control
- i18n Ready - Includes app-level translations out of the box
API Reference
connectSupersaas()
Factory function for SuperSaaS proxy mode.
function connectSupersaas(config: SupersaasConnectorConfig): ExternalCollectionConfig
Parameters:
interface SupersaasConnectorConfig {
sourceEndpoint: string // e.g., 'members' for /api/teams/[id]/members
schema: z.ZodSchema // Zod validation schema
transform: (item: any) => { id: string; title: string; [key: string]: any }
meta?: {
label?: string
description?: string
}
}
useUsers / useTeamMembers
Pre-configured collection configs for SuperSaaS (copy-paste mode).
import { usersConfig, useUsers } from '@friendlyinternet/nuxt-crouton-supersaas/supersaas'
import { teamMembersConfig, useTeamMembers } from '@friendlyinternet/nuxt-crouton-supersaas/supersaas'
// usersConfig: Minimal user reference (id, title, email)
// teamMembersConfig: Full team member data (includes role, teamId)
Related Documentation
- CroutonReferenceSelect Component - How to use reference selects in forms
- External Collections Guide - Conceptual guide
- Package Architecture - Understanding addon packages
- Internationalization - i18n integration
Need Help?
- GitHub Issues: nuxt-crouton/issues
- Connector Requests: Use "Enhancement" label for new connectors
- Bug Reports: Use "SuperSaaS" label
- Discussions: Share your integration patterns and use cases