A comprehensive NestJS foundation package providing JSON:API compliant APIs, Neo4j graph database integration, Redis caching, LangChain-based AI agents (including GraphRAG and DRIFT), OAuth 2.0 server, OpenAPI documentation, and common utilities for building modern multi-tenant applications.
- Features
- Architecture
- Installation
- Environment Variables
- Quick Start
- Company-User Model (B2B & B2C)
- Required Configuration Files
- Core Modules
- Health Check Endpoints
- Foundation Modules
- AI Agents
- DRIFT Module (Advanced Semantic Search)
- OpenAPI Documentation
- OAuth 2.0 Support
- Security & Authentication
- Company Deletion Handler
- Entity Descriptors (defineEntity)
- Customizing Agent Prompts
- License
- Dual-Mode Architecture: Run as API server (HTTP endpoints) or Worker (background job processing) from the same codebase
- JSON:API Compliance: Full JSON:API specification support with serializers, pagination, and cursor-based navigation
- Neo4j Integration: Graph database operations with Cypher query builder
- Redis Caching: Built-in caching layer with configurable TTLs
- Multi-Tenant Architecture: Support for both B2B (multi-company) and B2C (single invisible company) scenarios
- AI Agents: LangChain-powered agents including GraphRAG and DRIFT for knowledge extraction, summarization, and intelligent responses
- DRIFT Search: Advanced semantic search using community detection and HyDE (Hypothetical Document Embedding)
- OAuth 2.0 Server: RFC 6749/7636 compliant authorization server with PKCE support
- OpenAPI Documentation: Auto-generated JSON:API compliant Swagger/Redoc documentation
- Authentication: JWT-based authentication with role-based access control
- Background Jobs: BullMQ integration for async job processing
- WebSockets: Real-time communication support
- Vision LLM Support: Separate configuration for vision/image analysis models
- Transcriber Support: Speech-to-text transcription capabilities
- Tracing: OpenTelemetry integration for distributed tracing
- Logging: Structured logging with Loki integration
The library is designed to run in two modes from the same codebase:
- Handles HTTP requests via Fastify
- WebSocket connections for real-time features
- Uses
JwtAuthGuardfor authentication - Adds jobs to BullMQ queues
- Processes BullMQ jobs asynchronously
- Runs scheduled tasks (cron jobs)
- Discord bot integration (when configured)
- No HTTP server - just job processing
- Same configuration and modules as API
# Start API server
node dist/main --mode=api
# Start Worker (in separate process)
node dist/main --mode=worker
# Or use the npm scripts
pnpm start:prod # API mode
pnpm start:worker:prod # Worker modeThe mode is determined by the --mode flag and configured via getAppMode() and getAppModeConfig().
The library is organized into five main layers:
@carlonicora/nestjs-neo4jsonapi
├── common/ # Shared utilities, abstracts, decorators, guards
├── config/ # Configuration system and tokens
├── core/ # Infrastructure modules (19 modules)
├── foundations/ # Domain/business modules (31 modules)
├── agents/ # AI agent modules (7 modules)
├── openapi/ # OpenAPI/Swagger documentation
└── bootstrap/ # Application bootstrap utilities
pnpm add @carlonicora/nestjs-neo4jsonapiIf you want to use the package as a git submodule (for development or before npm release):
1. Add the submodule
cd /path/to/your-project
git submodule add https://github.com/carlonicora/nestjs-neo4jsonapi packages/nestjs-neo4jsonapi2. Verify it worked
git submodule status
# Should show: <commit-sha> packages/nestjs-neo4jsonapi (heads/master)3. Commit the submodule
git add .gitmodules packages/nestjs-neo4jsonapi
git commit -m "Add nestjs-neo4jsonapi as submodule"4. Update your package.json (e.g., apps/api/package.json)
{
"dependencies": {
"@carlonicora/nestjs-neo4jsonapi": "workspace:*"
}
}5. Ensure pnpm-workspace.yaml includes packages
packages:
- "apps/*"
- "packages/*"6. Install and build
pnpm install
cd packages/nestjs-neo4jsonapi && pnpm build && cd ../..For CI/CD (GitHub Actions), add submodules: recursive to your checkout step:
- uses: actions/checkout@v4
with:
submodules: recursiveCloning a project with submodules:
# When cloning fresh
git clone --recurse-submodules https://github.com/your/repo.git
# If already cloned
git submodule update --init --recursiveThe following packages must be installed in your application:
pnpm add @nestjs/common @nestjs/core @nestjs/config @nestjs/event-emitter @nestjs/jwt @nestjs/passport @nestjs/platform-socket.io @nestjs/throttler @nestjs/websockets nestjs-cls zod| Package | Version | Purpose |
|---|---|---|
nestjs-cls |
^6.0.1 | Request-scoped context (CLS) |
zod |
^4.0.0 | Schema validation |
Important: These are peer dependencies to ensure your application and the library share the same package instances, preventing NestJS dependency injection issues.
Create a .env file with the following configuration:
# Environment
ENV=development
# API
API_URL=http://localhost:3000/
API_PORT=3000
# App (frontend URL)
APP_URL=http://localhost:3001
# Neo4j
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your-password
NEO4J_DATABASE=neo4j
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_USERNAME=
REDIS_QUEUE=default
# Cache
CACHE_ENABLED=true
CACHE_DEFAULT_TTL=600
CACHE_SKIP_PATTERNS=/access,/auth,/notifications,/websocket,/version
# JWT Authentication
JWT_SECRET=your-jwt-secret
JWT_EXPIRES_IN=1h
# Auth
ALLOW_REGISTRATION=true
# OAuth 2.0 Server (optional)
OAUTH_ENABLED=false
OAUTH_AUTHORIZATION_CODE_LIFETIME=600
OAUTH_ACCESS_TOKEN_LIFETIME=3600
OAUTH_REFRESH_TOKEN_LIFETIME=604800
OAUTH_REQUIRE_PKCE_FOR_PUBLIC_CLIENTS=true
OAUTH_ROTATE_REFRESH_TOKENS=true
# CORS
CORS_ORIGINS=http://localhost:3001
CORS_CREDENTIALS=true
CORS_ORIGIN_PATTERNS=
CORS_METHODS=GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS
CORS_ALLOWED_HEADERS=
CORS_MAX_AGE=86400
CORS_PREFLIGHT_CONTINUE=false
CORS_OPTIONS_SUCCESS_STATUS=204
CORS_LOG_VIOLATIONS=true
# AI Configuration (optional)
AI_PROVIDER=openai
AI_API_KEY=sk-...
AI_MODEL=gpt-4o-mini
AI_URL=
AI_REGION=
AI_INSTANCE=
AI_API_VERSION=
AI_INPUT_COST_PER_1M_TOKENS=0
AI_OUTPUT_COST_PER_1M_TOKENS=0
AI_GOOGLE_CREDENTIALS_BASE64=
# Vision LLM (optional - falls back to AI_ settings if not set)
VISION_PROVIDER=openai
VISION_API_KEY=
VISION_MODEL=gpt-4o
VISION_URL=
VISION_REGION=
VISION_SECRET=
VISION_INSTANCE=
VISION_API_VERSION=
VISION_INPUT_COST_PER_1M_TOKENS=0
VISION_OUTPUT_COST_PER_1M_TOKENS=0
VISION_GOOGLE_CREDENTIALS_BASE64=
# Transcriber (optional)
TRANSCRIBER_PROVIDER=
TRANSCRIBER_API_KEY=
TRANSCRIBER_MODEL=
TRANSCRIBER_URL=
TRANSCRIBER_API_VERSION=
# Embedder (optional)
EMBEDDER_PROVIDER=openrouter
EMBEDDER_API_KEY=sk-...
EMBEDDER_MODEL=openai/text-embedding-3-large
EMBEDDER_DIMENSIONS=3072
EMBEDDER_INSTANCE=
EMBEDDER_API_VERSION=
EMBEDDER_REGION=
EMBEDDER_GOOGLE_CREDENTIALS_BASE64=
# Logging - Loki (optional)
LOKI_ENABLED=false
LOKI_HOST=http://localhost:3100
LOKI_USERNAME=
LOKI_PASSWORD=
LOKI_BATCHING=true
LOKI_INTERVAL=30
LOKI_APP_LABEL=
# Tracing - Tempo (optional)
TEMPO_ENABLED=false
TEMPO_ENDPOINT=http://localhost:4318/v1/traces
TEMPO_SERVICE_NAME=my-app
TEMPO_SERVICE_VERSION=1.0.0
# S3 Storage (optional)
S3_TYPE=aws
S3_ENDPOINT=
S3_BUCKET=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_REGION=eu-west-1
# Email (supports: sendgrid, smtp, brevo)
EMAIL_PROVIDER=sendgrid
EMAIL_API_KEY=
EMAIL_FROM=noreply@example.com
EMAIL_HOST=
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USERNAME=
EMAIL_PASSWORD=
# Stripe (optional)
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_API_VERSION=2024-12-18.acacia
STRIPE_PORTAL_RETURN_URL=
STRIPE_PORTAL_CONFIGURATION_ID=
# Push Notifications (optional)
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_EMAIL=
# Rate Limiting
RATE_LIMIT_ENABLED=true
RATE_LIMIT_TTL=60000
RATE_LIMIT_REQUESTS=100
IP_RATE_LIMIT_REQUESTS=20
# Encryption
ENCRYPTION_KEY=your-32-char-encryption-key
# Discord (optional)
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_TOKEN=
DISCORD_DEV_GUILD_ID=
# Google (optional)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=The library provides a bootstrap() function that handles all the complexity of setting up a NestJS application. You only need to provide your app-specific configuration.
What you need:
main.ts- Bootstrap entry point (~25 lines)config/config.ts- Optional custom config extending baseConfigfeatures/features.modules.ts- Your feature modules
What the library handles internally:
- AppModule creation (no
app.module.tsneeded) - Fastify adapter with multipart support
- Global validation pipes, exception filters, and interceptors
- Rate limiting, CORS, and caching
- i18n internationalization
- OpenAPI/Swagger documentation
- Discord bot integration (Worker mode)
- Graceful shutdown handlers
// src/features/features.modules.ts
import { Module } from "@nestjs/common";
// Import your app-specific feature modules
@Module({
imports: [
// Your feature modules here
],
})
export class FeaturesModules {}If you need custom queues or content types, create a config file:
// src/config/config.ts
import { baseConfig } from "@carlonicora/nestjs-neo4jsonapi";
import { QueueId } from "./enums/queue.id";
export default () => ({
...baseConfig,
// Register queue IDs for background job processing
chunkQueues: {
queueIds: Object.values(QueueId),
},
// Register content type labels for multi-label Neo4j queries
contentTypes: {
types: ["Article", "Document", "Hyperlink"],
},
});// src/openapi/openapi.config.ts
import { OpenApiOptions } from "@carlonicora/nestjs-neo4jsonapi";
import { allEntityDescriptors } from "./entity-registry";
export function getOpenApiConfig(): OpenApiOptions {
const isDevelopment = process.env.NODE_ENV !== "production";
return {
enableSwagger: isDevelopment || process.env.ENABLE_SWAGGER === "true",
swaggerPath: "/api-docs",
enableRedoc: isDevelopment || process.env.ENABLE_REDOC === "true",
redocPath: "/docs",
entityDescriptors: [...allEntityDescriptors],
title: "My API",
description: "RESTful API following JSON:API specification",
version: process.env.npm_package_version || "1.0.0",
bearerAuth: true,
contactEmail: "api@example.com",
license: "Proprietary",
licenseUrl: "https://example.com/terms",
};
}// src/main.ts
import * as dotenv from "dotenv";
import * as path from "path";
// Load environment variables FIRST (before any library imports)
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
import { bootstrap } from "@carlonicora/nestjs-neo4jsonapi";
import config from "./config/config";
import { FeaturesModules } from "./features/features.modules";
import { getOpenApiConfig } from "./openapi/openapi.config";
bootstrap({
appModules: [FeaturesModules],
i18n: {
fallbackLanguage: "en",
path: path.join(__dirname, "i18n"),
},
config: config,
contentExtension: {
additionalRelationships: [],
},
openApi: getOpenApiConfig(),
});That's it! The bootstrap() function handles everything else.
| Option | Type | Required | Description |
|---|---|---|---|
appModules |
(Type<any> | DynamicModule)[] |
Yes | Your app-specific feature modules |
i18n |
I18nOptions |
No | i18n configuration (fallbackLanguage, path) |
config |
() => Record<string, any> |
No | Custom config that extends baseConfig (merged with library defaults) |
contentExtension |
ContentExtensionOptions |
No | Additional relationships for Content module |
openApi |
OpenApiOptions |
No | OpenAPI/Swagger documentation configuration |
The config function returns an object that is merged with baseConfig. Available options:
| Option | Type | Description |
|---|---|---|
chunkQueues.queueIds |
string[] |
Queue IDs for BullMQ registration (for background job processing) |
contentTypes.types |
string[] |
Neo4j labels for content types (used in multi-label content queries) |
jobNames |
{ process: Record<string, string>, notifications?: Record<string, string> } |
Job names for BullMQ processors (maps content types to job names) |
prompts.* |
Various | Custom AI agent prompts (see Customizing Agent Prompts) |
The bootstrap() function creates a dynamic AppModule that includes:
- EventEmitterModule for async events
- AppModeModule (API vs Worker mode)
- ConfigModule with merged configuration
- ThrottlerModule for rate limiting
- ClsModule for request context
- I18nModule for internationalization
- ScheduleModule for cron jobs (Worker mode only)
- CoreModule (all infrastructure modules)
- FoundationsModule (all domain modules)
- AgentsModule (AI agents)
- OpenApiModule for documentation
- NecordModule + DiscordModule (Worker mode, when DISCORD_TOKEN is set)
Note: For advanced customization scenarios, you can examine the bootstrap source code in
packages/nestjs-neo4jsonapi/src/bootstrap/. However, the defaultbootstrap()function handles all common use cases.
The library implements a flexible multi-tenant architecture that supports both B2B (Business-to-Business) and B2C (Business-to-Consumer) scenarios through the Company-User relationship.
Company (1) <--[BELONGS_TO]-- (*) User
|
+--[HAS_MODULE]--> Module (features available to company)
+--[HAS_FEATURE]--> Feature (feature flags)
type User = {
id: string;
email: string;
name?: string;
password?: string;
avatar?: string;
isActive: boolean;
isDeleted: boolean;
role?: Role[]; // User's roles within the company
company?: Company; // The company this user belongs to
module?: Module[]; // Modules assigned to this specific user
};type Company = {
id: string;
name: string;
logo?: string;
isActiveSubscription: boolean;
ownerEmail: string;
monthlyTokens: number;
oneOffTokens: number;
feature: Feature[]; // Features available to company
module: Module[]; // Modules available to company
};Important: All role IDs in the library are UUIDs, not string names. The library provides base system roles that you can extend:
// src/config/roles.ts
import { SystemRoles } from "@carlonicora/nestjs-neo4jsonapi";
/**
* Extend the base SystemRoles with your application-specific roles.
* All role IDs MUST be UUIDs.
*/
export const AppRoles = {
// Base roles from the library
...SystemRoles,
// Your application-specific roles (UUIDs)
Manager: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
Editor: "b2c3d4e5-f6a7-8901-bcde-f12345678901",
Viewer: "c3d4e5f6-a7b8-9012-cdef-123456789012",
} as const;
export type AppRoleId = (typeof AppRoles)[keyof typeof AppRoles];The base SystemRoles includes:
Administrator:"53394cb8-1e87-11ef-8b48-bed54b8f8aba"- System-wide adminCompanyAdministrator:"2e1eee00-6cba-4506-9059-ccd24e4ea5b0"- Company-level admin
In a B2B application, companies are visible and central to the user experience:
- Each company has multiple users
- Users see company branding, shared data, and collaborate within their company
- Company administrators manage users, modules, and settings
- Data is segregated by company
import { SystemRoles } from "@carlonicora/nestjs-neo4jsonapi";
import { AppRoles } from "./config/roles";
// Example: User registration in B2B
async function registerB2BUser(email: string, companyId: string) {
// User explicitly joins an existing company
// Note: roles must be UUIDs, not string names!
const user = await userService.create({
email,
companyId, // Links to existing company
roles: [AppRoles.Viewer], // UUID: "c3d4e5f6-a7b8-9012-cdef-123456789012"
});
}
// Example: Create company admin
async function createCompanyAdmin(email: string, companyId: string) {
const user = await userService.create({
email,
companyId,
roles: [SystemRoles.CompanyAdministrator], // UUID from library
});
}In a B2C application, companies are invisible but still exist in the database:
- Each user gets their own "personal" company created automatically
- The company provides data isolation and the same multi-tenant security
- Users are unaware they have a company - it's an implementation detail
- This allows future upgrades to B2B (invite team members) without restructuring
import { SystemRoles } from "@carlonicora/nestjs-neo4jsonapi";
// Example: User registration in B2C
async function registerB2CUser(email: string) {
// Create a personal/invisible company for this user
const company = await companyService.create({
name: `${email}'s workspace`, // Or generate a UUID
ownerEmail: email,
});
// Create user linked to their personal company
// They are the administrator of their own space
const user = await userService.create({
email,
companyId: company.id,
roles: [SystemRoles.CompanyAdministrator], // UUID - owner of personal space
});
}- JWT Token: Contains
userId,companyId, androles - JwtAuthGuard: Validates token and loads company configurations via
COMPANY_CONFIGURATIONS_FACTORY - CLS Context: Stores
companyIdanduserIdfor the request lifecycle - Neo4j Queries: Automatically scoped to
$companyIdviainitQuery()
// All queries are automatically company-scoped
const query = neo4jService.initQuery({ serialiser: UserModel });
query.query = `
MATCH (company:Company {id: $companyId})
MATCH (user:User)-[:BELONGS_TO]->(company)
RETURN user
`;
// $companyId is automatically injected from CLS context| Benefit | B2B | B2C |
|---|---|---|
| Data isolation | Per company | Per user (via invisible company) |
| User collaboration | Yes | No (single user) |
| Scalability | Multi-tenant | Same architecture |
| Future B2B upgrade | Already supported | Easy migration path |
| Billing | Per company | Per user (mapped to company) |
your-app/
├── src/
│ ├── config/
│ │ ├── config.ts # App configuration (optional)
│ │ └── enums/
│ │ └── queue.id.ts # Queue identifiers (if using jobs)
│ ├── features/ # Your app-specific modules
│ │ └── features.modules.ts # Imports all your feature modules
│ ├── openapi/ # OpenAPI config (optional)
│ │ └── openapi.config.ts
│ └── main.ts # Bootstrap entry (~25 lines)
├── .env
└── package.json
Note: No app.module.ts is required - the library creates this dynamically via createAppModule().
Queue IDs must match the lowercase version of your content type labelName:
// src/config/enums/queue.id.ts
export enum QueueId {
CHUNK = "chunk", // Required - used by ChunkProcessor
ARTICLE = "article", // For Article content type (labelName: "Article")
DOCUMENT = "document", // For Document content type (labelName: "Document")
// Add queue IDs for each content type (lowercase of labelName)
}Job names map content types to processor job names:
// src/config/enums/job.name.ts
export const JobName = {
process: {
chunk: "process_chunk", // Required - used by ChunkProcessor
Article: "process_article", // Key = labelName, value = job name
Document: "process_document",
},
notifications: {},
} as const;Convention: After chunk processing completes, ChunkService automatically queues a job to labelName.toLowerCase() queue with job name from jobNames.process[labelName].
The library includes 19 core infrastructure modules:
| Module | Description |
|---|---|
Neo4JModule |
Neo4j graph database integration |
RedisModule |
Redis client and messaging |
CacheModule |
Distributed caching layer |
SecurityModule |
JWT authentication and role-based access control |
JsonApiModule |
JSON:API specification compliance |
LoggingModule |
Structured logging with Loki |
TracingModule |
Distributed tracing with OpenTelemetry |
EmailModule |
Email service (SendGrid, SMTP, Brevo) |
QueueModule |
BullMQ job queue processing |
WebsocketModule |
Real-time WebSocket communication |
CorsModule |
CORS configuration |
VersionModule |
API versioning |
StripeModule |
Stripe payment integration |
LLMModule |
LLM service for AI operations |
BlockNoteModule |
Block editor support |
MigratorModule |
Database migrations |
AppModeModule |
Application mode (API/Worker) |
DebugModule |
Debugging utilities |
HealthModule |
Health check endpoints for liveness/readiness |
The package includes built-in health check endpoints using @nestjs/terminus for container orchestration and load balancer integration. Rate limiting is automatically disabled for all health endpoints.
| Endpoint | Purpose | Checks |
|---|---|---|
GET /health |
Full health status | Neo4j, Redis, S3, Disk |
GET /health/live |
Liveness probe | None (process running) |
GET /health/ready |
Readiness probe | Neo4j, Redis |
Returns detailed status of all dependencies. Use for monitoring dashboards.
Response when healthy (200 OK):
{
"status": "ok",
"info": {
"neo4j": { "status": "up", "message": "Neo4j connection healthy" },
"redis": { "status": "up", "message": "Redis connection healthy" },
"storage": { "status": "up", "message": "aws storage connection healthy" },
"disk": { "status": "up", "message": "Disk space healthy", "free": "50.00 GB" }
},
"error": {},
"details": { ... }
}Indicates if the application process is running. Does NOT check external dependencies.
Use for Kubernetes livenessProbe: If this fails, the container should be restarted.
Response (200 OK):
{
"status": "ok",
"info": {},
"error": {},
"details": {}
}Indicates if the application can accept traffic. Checks critical dependencies (Neo4j, Redis).
Use for Kubernetes readinessProbe: If this fails, traffic should be routed elsewhere.
Response when healthy (200 OK):
{
"status": "ok",
"info": {
"neo4j": { "status": "up", "message": "Neo4j connection healthy" },
"redis": { "status": "up", "message": "Redis connection healthy" }
},
"error": {},
"details": { ... }
}Response when unhealthy (503 Service Unavailable):
{
"status": "error",
"info": {
"redis": { "status": "up", "message": "Redis connection healthy" }
},
"error": {
"neo4j": { "status": "down", "message": "Connection refused" }
},
"details": { ... }
}apiVersion: v1
kind: Pod
spec:
containers:
- name: api
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5The module includes four health indicators:
| Indicator | Timeout | What it checks |
|---|---|---|
Neo4jHealthIndicator |
3s | Executes RETURN 1 query |
RedisHealthIndicator |
3s | Connection status + PING |
S3HealthIndicator |
5s | Bucket access (HeadBucket) |
DiskHealthIndicator |
- | Free space >= 1GB or 10% |
The library includes 31 foundation modules for business domain logic:
| Module | Description |
|---|---|
UserModule |
User management with CRUD operations |
CompanyModule |
Multi-tenant company management with deletion |
AuthModule |
Authentication (login, register, password reset) |
RoleModule |
Role management |
OAuthModule |
OAuth 2.0 Authorization Server (RFC 6749/7636) |
ChunkModule |
Document chunk storage and retrieval |
ChunkerModule |
Document parsing (PDF, DOCX, XLSX, HTML, PPTX) |
AtomicFactModule |
Atomic facts management for knowledge graphs |
KeyConceptModule |
Key concepts for knowledge graphs |
CommunityModule |
Knowledge graph community storage |
ContentModule |
Content management with extension support |
NotificationModule |
User notifications |
PushModule |
Push notifications (VAPID) |
FeatureModule |
Feature flag management |
ModuleModule |
Module/plugin management |
S3Module |
S3-compatible storage |
TokenUsageModule |
AI token usage tracking |
AuditModule |
Audit logging |
RelevancyModule |
Relevancy scoring |
DiscordModule |
Discord bot integration |
DiscordUserModule |
Discord user management |
GoogleUserModule |
Google OAuth user management |
| Stripe Sub-modules: | |
StripeModule |
Core Stripe integration |
StripeCustomerModule |
Stripe customer management |
StripeSubscriptionModule |
Subscription lifecycle management |
StripeProductModule |
Product management |
StripePriceModule |
Price/plan management with feature selection |
StripeInvoiceModule |
Invoice management |
StripeUsageModule |
Usage-based billing |
StripeWebhookModule |
Webhook processing via BullMQ |
StripeTrialModule |
Trial period management with email notifications |
The library includes 7 LangChain-powered agent modules for intelligent document processing:
Extracts knowledge graphs from text, including atomic facts and key concept relationships.
import { GraphCreatorService } from "@carlonicora/nestjs-neo4jsonapi";
@Injectable()
export class MyService {
constructor(private readonly graphCreator: GraphCreatorService) {}
async extractKnowledge(text: string) {
const result = await this.graphCreator.generateGraph({ content: text });
// result contains: atomicFacts, keyConceptsRelationships, tokens
return result;
}
}Implements GraphRAG (Graph-based Retrieval Augmented Generation) for intelligent context gathering:
- Uses a knowledge graph structure (Neo4j)
- Traverses atomic facts and key concepts
- Explores neighbouring nodes for richer context
import { ContextualiserService } from "@carlonicora/nestjs-neo4jsonapi";
@Injectable()
export class MyService {
constructor(private readonly contextualiser: ContextualiserService) {}
async gatherContext(question: string) {
return this.contextualiser.contextualise({ question });
}
}Generates summaries from document chunks using a map-reduce pattern.
import { SummariserService } from "@carlonicora/nestjs-neo4jsonapi";
@Injectable()
export class MyService {
constructor(private readonly summariser: SummariserService) {}
async summarize(chunks: Chunk[]) {
const result = await this.summariser.summarise({ chunks });
// result contains: content, tldr, tokens
return result;
}
}Generates comprehensive answers based on gathered context.
import { ResponderService } from "@carlonicora/nestjs-neo4jsonapi";
@Injectable()
export class MyService {
constructor(private readonly responder: ResponderService) {}
async generateAnswer(context: any) {
return this.responder.respond(context);
}
}Detects and creates communities from knowledge graph structure.
import { CommunityDetectorService } from "@carlonicora/nestjs-neo4jsonapi";
@Injectable()
export class MyService {
constructor(private readonly communityDetector: CommunityDetectorService) {}
async detectCommunities() {
return this.communityDetector.detect();
}
}Generates summaries for detected communities.
import { CommunitySummariserService } from "@carlonicora/nestjs-neo4jsonapi";
@Injectable()
export class MyService {
constructor(private readonly summariser: CommunitySummariserService) {}
async summarizeCommunity(communityId: string) {
return this.summariser.summarize({ communityId });
}
}See DRIFT Module (Advanced Semantic Search) for detailed documentation.
DRIFT (Dynamic Retrieval with Intelligence and Future-aware Thinking) is an advanced semantic search system that uses community detection and HyDE (Hypothetical Document Embedding) for sophisticated query understanding.
DRIFT uses a multi-node workflow:
| Node | Purpose |
|---|---|
HydeNodeService |
Generates hypothetical document embedding |
CommunitySearchNodeService |
Vector search against community summaries |
PrimerAnswerNodeService |
Generates initial answer + follow-up questions |
FollowUpNodeService |
Processes follow-up questions iteratively |
SynthesisNodeService |
Combines all answers into final response |
import { DriftSearchService } from "@carlonicora/nestjs-neo4jsonapi";
@Injectable()
export class MyService {
constructor(private readonly driftSearch: DriftSearchService) {}
async searchWithDrift(question: string) {
// Full DRIFT workflow: HyDE -> Community Search -> Primer Answer -> Follow-ups -> Synthesis
const result = await this.driftSearch.search({
question,
config: {
primerTopK: 5, // Top K communities to search
followUpDepth: 2, // Depth of follow-up question iterations
},
});
// result contains:
// - answer: Final synthesized answer
// - matchedCommunities: Communities that matched the query
// - followUpAnswers: Answers from follow-up questions
// - initialAnswer: Initial answer before follow-ups
// - confidence: Confidence score
// - hydeEmbedding: Generated hypothetical document embedding
return result;
}
async quickSearch(question: string) {
// Quick search without follow-ups (HyDE + Community Search + Primer only)
return this.driftSearch.quickSearch({ question, topK: 5 });
}
}- HyDE Generation: Creates a hypothetical document that would answer the question
- Community Search: Uses the HyDE embedding to find relevant communities
- Primer Answer: Generates an initial answer and identifies follow-up questions
- Follow-up Processing: Recursively explores follow-up questions for deeper context
- Synthesis: Combines all gathered information into a comprehensive final answer
The library includes comprehensive OpenAPI/Swagger documentation support with JSON:API compliance.
Enable OpenAPI in your bootstrap options:
import { bootstrap } from "@carlonicora/nestjs-neo4jsonapi";
bootstrap({
appModules: [FeaturesModules],
openApi: {
enableSwagger: true,
swaggerPath: '/api-docs',
enableRedoc: true,
redocPath: '/docs',
title: 'My API',
description: 'RESTful API following JSON:API specification',
version: '1.0.0',
bearerAuth: true,
contactEmail: 'api@example.com',
license: 'Proprietary',
licenseUrl: 'https://example.com/terms',
entityDescriptors: [
PhotographDescriptor,
RollDescriptor,
// ... your entity descriptors
],
},
});| Option | Type | Default | Description |
|---|---|---|---|
enableSwagger |
boolean | false | Enable Swagger UI endpoint |
swaggerPath |
string | '/api-docs' | Path for Swagger UI |
enableRedoc |
boolean | false | Enable Redoc endpoint |
redocPath |
string | '/docs' | Path for Redoc |
title |
string | - | API documentation title |
description |
string | - | API description (supports markdown) |
version |
string | - | API version |
bearerAuth |
boolean | true | Enable JWT Bearer auth in docs |
contactEmail |
string | - | Contact email |
license |
string | - | License name |
licenseUrl |
string | - | License URL |
entityDescriptors |
array | [] | Entity descriptors for schema generation |
import {
ApiJsonApiResponse,
ApiJsonApiListQuery,
ApiJsonApiListErrors,
ApiJsonApiCreateErrors,
ApiJsonApiDeleteErrors,
} from "@carlonicora/nestjs-neo4jsonapi";
@Controller('photographs')
export class PhotographController {
@Get(':id')
@ApiJsonApiResponse(PhotographDescriptor)
async findById() { ... }
@Get()
@ApiJsonApiResponse(PhotographDescriptor, { isList: true })
@ApiJsonApiListQuery()
@ApiJsonApiListErrors()
async findAll() { ... }
@Post()
@ApiJsonApiResponse(PhotographDescriptor, { status: 201 })
@ApiJsonApiCreateErrors()
async create() { ... }
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiJsonApiDeleteErrors()
async delete() { ... }
}The library implements a complete OAuth 2.0 Authorization Server following RFC 6749 and RFC 7636 (PKCE).
OAUTH_ENABLED=true
OAUTH_AUTHORIZATION_CODE_LIFETIME=600
OAUTH_ACCESS_TOKEN_LIFETIME=3600
OAUTH_REFRESH_TOKEN_LIFETIME=604800
OAUTH_REQUIRE_PKCE_FOR_PUBLIC_CLIENTS=true
OAUTH_ROTATE_REFRESH_TOKENS=true| Endpoint | Method | Purpose |
|---|---|---|
/oauth/authorize |
GET | Authorization endpoint |
/oauth/token |
POST | Token endpoint |
/oauth/revoke |
POST | Token revocation (RFC 7009) |
/oauth/introspect |
POST | Token introspection (RFC 7662) |
/oauth/clients |
CRUD | Client management |
import {
JwtOrOAuthGuard,
OAuthScopes,
} from "@carlonicora/nestjs-neo4jsonapi";
@Controller('photographs')
export class PhotographController {
// Accepts both JWT and OAuth tokens (migration path)
@Get(':id')
@UseGuards(JwtOrOAuthGuard)
@OAuthScopes('photographs:read')
async findById() { ... }
@Post()
@UseGuards(JwtOrOAuthGuard)
@OAuthScopes('photographs:read', 'photographs:write')
async create() { ... }
}When using OAuth guards, the following context is set in CLS:
// In your service
const userId = this.cls.get('userId');
const companyId = this.cls.get('companyId');
const clientId = this.cls.get('oauthClientId');
const scopes = this.cls.get('oauthScopes');
const authType = this.cls.get('authType'); // 'oauth' or 'jwt'| Guard | Purpose |
|---|---|
JwtAuthGuard |
Requires valid JWT token |
AdminJwtAuthGuard |
Requires Administrator role |
OptionalJwtAuthGuard |
JWT optional (works for anonymous users) |
JwtOrOAuthGuard |
Accepts OAuth first, falls back to JWT |
import { Controller, Get, UseGuards } from "@nestjs/common";
import {
JwtAuthGuard,
AdminJwtAuthGuard,
OptionalJwtAuthGuard,
JwtOrOAuthGuard,
Roles,
OAuthScopes,
SystemRoles
} from "@carlonicora/nestjs-neo4jsonapi";
import { AppRoles } from "./config/roles";
@Controller("api/resources")
export class ResourceController {
// Requires valid JWT token
@Get()
@UseGuards(JwtAuthGuard)
async getResources() { ... }
// Requires Administrator role (uses AdminJwtAuthGuard)
@Get("admin")
@UseGuards(AdminJwtAuthGuard)
async getAdminResources() { ... }
// JWT is optional - works for both authenticated and anonymous users
@Get("public")
@UseGuards(OptionalJwtAuthGuard)
async getPublicResources() { ... }
// Accepts both JWT and OAuth tokens
@Get("oauth-or-jwt")
@UseGuards(JwtOrOAuthGuard)
@OAuthScopes('resources:read')
async getResourcesWithOAuth() { ... }
// Requires specific roles (UUIDs)
@Get("restricted")
@UseGuards(JwtAuthGuard)
@Roles(SystemRoles.Administrator, SystemRoles.CompanyAdministrator)
async getRestrictedResources() { ... }
// Using your custom app roles
@Get("managers-only")
@UseGuards(JwtAuthGuard)
@Roles(AppRoles.Manager, SystemRoles.Administrator)
async getManagerResources() { ... }
}import { Injectable } from "@nestjs/common";
import { ClsService } from "nestjs-cls";
@Injectable()
export class MyService {
constructor(private readonly cls: ClsService) {}
async doSomething() {
// Access current user/company context
const userId = this.cls.get("userId");
const companyId = this.cls.get("companyId");
const roles = this.cls.get("roles");
const language = this.cls.get("language");
// OAuth-specific context (when using OAuth)
const authType = this.cls.get("authType"); // 'oauth' or 'jwt'
const oauthScopes = this.cls.get("oauthScopes");
if (config?.hasModule("premium-feature")) {
// User's company has access to premium feature
}
}
}The library supports custom company deletion handlers for application-specific cleanup.
import {
CompanyDeletionHandler,
COMPANY_DELETION_HANDLER,
DeletionReason,
DeletionOptions
} from "@carlonicora/nestjs-neo4jsonapi";
type DeletionReason = 'trial_expired' | 'subscription_cancelled' | 'immediate_deletion';
interface DeletionOptions {
sendEmail?: boolean;
reason?: DeletionReason;
}
interface CompanyDeletionHandler {
deleteCompany(companyId: string, companyName: string, options?: DeletionOptions): Promise<void>;
}import { Injectable } from "@nestjs/common";
import {
CompanyDeletionHandler,
DeletionOptions,
S3Service,
} from "@carlonicora/nestjs-neo4jsonapi";
@Injectable()
export class CompanyDeletionService implements CompanyDeletionHandler {
constructor(
private readonly s3: S3Service,
private readonly auditLogger: AuditLogger,
) {}
async deleteCompany(companyId: string, companyName: string, options?: DeletionOptions) {
// 1. Delete S3 objects
await this.s3.deleteCompanyObjects(companyId);
// 2. Log audit event
await this.auditLogger.log({
action: 'company_deleted',
companyId,
companyName,
reason: options?.reason,
});
// 3. Send notification email if requested
if (options?.sendEmail) {
await this.sendDeletionEmail(companyName, options.reason);
}
}
}import { Module, Global } from "@nestjs/common";
import { COMPANY_DELETION_HANDLER } from "@carlonicora/nestjs-neo4jsonapi";
@Global()
@Module({
providers: [
CompanyDeletionService,
{
provide: COMPANY_DELETION_HANDLER,
useExisting: CompanyDeletionService,
},
],
exports: [CompanyDeletionService, COMPANY_DELETION_HANDLER],
})
export class CompanyDeletionModule {}Entity Descriptors provide a single source of truth for entity configuration, auto-generating mappers, serializers, constraints, and indexes.
import { defineEntity, Entity, S3Service } from "@carlonicora/nestjs-neo4jsonapi";
import { rollMeta, companyMeta } from "@carlonicora/nestjs-neo4jsonapi";
// 1. Define entity type
export type Photograph = Entity & {
url: string;
filename?: string;
stars: number;
roll: Roll;
company: Company;
};
// 2. Create entity descriptor
export const PhotographDescriptor = defineEntity<Photograph>()({
// Meta (replaces .meta.ts file)
type: "photographs",
endpoint: "photographs",
nodeName: "photograph",
labelName: "Photograph",
// Inject services for field transformers
injectServices: [S3Service],
// Field definitions
fields: {
url: {
type: "string",
required: true,
transform: async (data, services) => {
return await services.S3Service.generateSignedUrl({ key: data.url });
},
},
filename: { type: "string" },
stars: { type: "number", default: 0 },
},
// Computed fields (from Neo4j record)
computed: {
position: {
compute: (params) => params.record.get("position"),
meta: true, // Goes to JSON:API meta
},
},
// Relationships
relationships: {
roll: {
model: rollMeta,
direction: "out",
relationship: "FRAME_OF",
cardinality: "one",
dtoKey: "roll",
fields: [{ name: "position", type: "number", required: true }],
},
company: {
model: companyMeta,
direction: "out",
relationship: "BELONGS_TO",
cardinality: "one",
},
},
});| Type | Neo4j/Cypher | JSON |
|---|---|---|
string |
STRING | string |
number |
INTEGER/FLOAT | number |
boolean |
BOOLEAN | boolean |
date |
DATE | string (ISO) |
datetime |
DATETIME | string (ISO) |
json |
MAP | object |
string[] |
LIST<STRING> | string[] |
number[] |
LIST<INTEGER> | number[] |
| Option | Type | Description |
|---|---|---|
type |
CypherType | Data type |
required |
boolean | Required field |
default |
any | Default value |
meta |
boolean | Put in JSON:API meta |
transform |
function | Async transform for serialization |
| Option | Type | Description |
|---|---|---|
model |
DataMeta | Related entity metadata |
direction |
'in' | 'out' | Relationship direction |
relationship |
string | Neo4j relationship type |
cardinality |
'one' | 'many' | Single or collection |
required |
boolean | Use MATCH vs OPTIONAL MATCH |
contextKey |
string | CLS context key for value |
dtoKey |
string | Override key in DTO |
fields |
array | Edge properties |
- Constraints: Unique constraint on
id - Indexes: FULLTEXT index on all string fields
- Mapper: Entity-to-record mapping
- Serializer: JSON:API compliant serialization
The library includes default prompts. Customization is entirely optional.
| Agent | Config Key | Purpose |
|---|---|---|
| GraphCreator | prompts.graphCreator |
Extract atomic facts and key concepts |
| Contextualiser | prompts.contextualiser.questionRefiner |
Refine user questions |
| Contextualiser | prompts.contextualiser.rationalPlan |
Create rational plans |
| Contextualiser | prompts.contextualiser.keyConceptExtractor |
Score key concepts |
| Contextualiser | prompts.contextualiser.atomicFactsExtractor |
Evaluate atomic facts |
| Contextualiser | prompts.contextualiser.chunk |
Assess text chunks |
| Contextualiser | prompts.contextualiser.chunkVector |
Vector-based chunk retrieval |
| Responder | prompts.responder |
Generate final answers |
| Summariser | prompts.summariser.map |
Summarize individual chunks |
| Summariser | prompts.summariser.combine |
Combine summaries |
| Summariser | prompts.summariser.tldr |
Create TLDR |
| CommunityDetector | prompts.communityDetector |
Community detection |
| CommunitySummariser | prompts.communitySummariser |
Community summarization |
| DRIFT | prompts.drift.hyde |
HyDE generation |
| DRIFT | prompts.drift.primerAnswer |
Initial answer generation |
| DRIFT | prompts.drift.followUp |
Follow-up question handling |
| DRIFT | prompts.drift.synthesis |
Final answer synthesis |
Prompts are configured via createBaseConfig():
// src/config/config.ts
import { createBaseConfig } from "@carlonicora/nestjs-neo4jsonapi";
export const config = createBaseConfig({
appName: "my-app",
prompts: {
graphCreator: "Your custom graph creator prompt...",
contextualiser: {
questionRefiner: "Your custom question refiner prompt...",
rationalPlan: "Your custom rational plan prompt...",
},
summariser: {
map: "Your custom map prompt...",
},
},
});Or extend baseConfig via BootstrapOptions.config:
// src/main.ts
bootstrap({
// ... other options
config: () => ({
prompts: {
graphCreator: "Your custom graph creator prompt...",
},
}),
});This project is licensed under GPL v3 for open source use.
For commercial/closed-source licensing, contact: @carlonicora
Carlo Nicora - @carlonicora