@memberjunction/server v2.48.0
@memberjunction/server
The @memberjunction/server
library provides a comprehensive API server for MemberJunction, featuring both GraphQL and REST APIs. It includes all the functions required to start up the server, manage authentication, handle database connections, and provide a robust interface for accessing and managing metadata within MemberJunction.
Key Features
- Dual API Support: Both GraphQL and REST APIs with consistent authentication
- Multi-Database Support: Read-write and optional read-only database connections
- Advanced Authentication: Support for MSAL (Azure AD) and Auth0 authentication providers
- Transaction Management: Automatic transaction wrapper for GraphQL mutations
- Type-Safe Resolvers: Full TypeScript support with type-graphql integration
- Entity Management: Comprehensive CRUD operations with change tracking
- Real-time Support: WebSocket subscriptions for GraphQL
- Compression: Built-in response compression for better performance
- Extensible Architecture: Support for custom resolvers and entity subclasses
- AI Integration: Built-in support for AI operations and learning cycle scheduling
- Security Features: Entity-level and schema-level access control for REST API
Installation
npm install @memberjunction/server
Dependencies
This package depends on several core MemberJunction packages:
@memberjunction/core
: Core functionality and metadata management@memberjunction/sqlserver-dataprovider
: SQL Server data provider@memberjunction/graphql-dataprovider
: GraphQL data provider@memberjunction/ai
: AI engine integration- Various other MJ packages for specific functionality
Configuration
The server uses configuration from its environment
Env variable | Description |
---|---|
DB_HOST | The hostname for the common data store database |
DB_PORT | The port for the common data store database (default 1433) |
DB_USERNAME | The username used to authenticate with the common data store |
DB_PASSWORD | The password used to authenticate with the common data store |
DB_DATABASE | The common data store database name |
PORT | The port used by the server (default 4000) |
ROOT_PATH | The GraphQL root path (default /) |
WEB_CLIENT_ID | The client ID used for MSAL authentication |
TENANT_ID | The tenant ID used for MSAL authentication |
ENABLE_INTROSPECTION | A flag to allow GraphQL introspection (default false) |
WEBSITE_RUN_FROM_PACKAGE | An Azure flag to indicate a read-only file system |
AUTH0_DOMAIN | The Auth0 domain |
AUTH0_CLIENT_ID | The Auth0 Client ID |
AUTH0_CLIENT_SECRET | The Auth0 Client secret |
MJ_CORE_SCHEMA | The core schema to use for the data provider |
CONFIG_FILE | An absolute path to the config file json |
DB_READ_ONLY_USERNAME | Username for read-only database connection (optional) |
DB_READ_ONLY_PASSWORD | Password for read-only database connection (optional) |
REST API
In addition to the GraphQL API, MemberJunction provides a REST API for applications that prefer RESTful architecture. By default, the REST API is enabled but can be disabled.
For comprehensive documentation on the REST API, including configuration options, security controls, and available endpoints, see REST_API.md.
The REST API supports:
- Standard CRUD operations for entities
- View operations for data retrieval
- Metadata exploration
- Wildcard pattern matching for entity filtering
- Schema-level access control
- Comprehensive security configuration
Usage
Basic Server Setup
Import the serve
function from the package and run it as part of the server's main function. The function accepts an array of absolute paths to the resolver code.
import { serve } from '@memberjunction/server';
import { resolve } from 'node:path';
const localPath = (p: string) => resolve(__dirname, p);
const resolverPaths = [
'resolvers/**/*Resolver.{js,ts}',
'generic/*Resolver.{js,ts}',
'generated/generated.ts',
]
serve(resolverPaths.map(localPath));
Advanced Server Options
The serve
function accepts an optional MJServerOptions
object:
import { serve, MJServerOptions } from '@memberjunction/server';
const options: MJServerOptions = {
onBeforeServe: async () => {
// Custom initialization logic
console.log('Server is about to start...');
},
restApiOptions: {
enabled: true,
includeEntities: ['User*', 'Entity*'],
excludeEntities: ['Password', 'APIKey*'],
includeSchemas: ['public'],
excludeSchemas: ['internal']
}
};
serve(resolverPaths.map(localPath), createApp(), options);
Transaction Management
The server automatically wraps GraphQL mutations in database transactions. This ensures data consistency when multiple operations are performed:
mutation {
CreateUser(input: { FirstName: "John", LastName: "Doe" }) {
ID
}
CreateUserRole(input: { UserID: "...", RoleID: "..." }) {
ID
}
}
// Both operations will be committed together or rolled back on error
Custom New User Behavior
The behavior to handle new users can be customized by subclassing the NewUserBase
class. The subclass can pre-process, post-process or entirely override the base class behavior as needed. Import the class before calling serve
to ensure the class is registered.
index.ts
import { serve } from '@memberjunction/server';
import { resolve } from 'node:path';
import './auth/exampleNewUserSubClass'; // make sure this new class gets registered
// ...
auth/exampleNewUserSubClass.ts
import { LogError, Metadata, RunView } from "@memberjunction/core";
import { RegisterClass } from "@memberjunction/global";
import { NewUserBase, configInfo } from '@memberjunction/server';
import { UserCache } from "@memberjunction/sqlserver-dataprovider";
/**
* This example class subclasses the @NewUserBase class and overrides the createNewUser method to create a new person record and then call the base class to create the user record. In this example there is an entity
* called "Persons" that is mapped to the User table in the core MemberJunction schema. You can sub-class the NewUserBase to do whatever behavior you want and pre-process, post-process or entirely override the base
* class behavior.
*/
@RegisterClass(NewUserBase, undefined, 1) /*by putting 1 into the priority setting, MJGlobal ClassFactory will use this instead of the base class as that registration had no priority*/
export class ExampleNewUserSubClass extends NewUserBase {
public override async createNewUser(firstName: string, lastName: string, email: string) {
try {
const md = new Metadata();
const contextUser = UserCache.Instance.Users.find(u => u.Email.trim().toLowerCase() === configInfo?.userHandling?.contextUserForNewUserCreation?.trim().toLowerCase())
if(!contextUser) {
LogError(`Failed to load context user ${configInfo?.userHandling?.contextUserForNewUserCreation}, if you've not specified this on your config.json you must do so. This is the user that is contextually used for creating a new user record dynamically.`);
return undefined;
}
const pEntity = md.Entities.find(e => e.Name === 'Persons'); // look up the entity info for the Persons entity
if (!pEntity) {
LogError('Failed to find Persons entity');
return undefined;
}
let personId;
// this block of code only executes if we have an entity called Persons
const rv = new RunView();
const viewResults = await rv.RunView({
EntityName: 'Persons',
ExtraFilter: `Email = '${email}'`
}, contextUser)
if (viewResults && viewResults.Success && Array.isArray(viewResults.Results) && viewResults.Results.length > 0) {
// we have a match so use it
const row = (viewResults.Results as { ID: number }[])[0]; // we know the rows will have an ID number
personId = row['ID'];
}
if (!personId) {
// we don't have a match so create a new person record
const p = await md.GetEntityObject('Persons', contextUser);
p.NewRecord(); // assumes we have an entity called Persons that has FirstName/LastName/Email fields
p.FirstName = firstName;
p.LastName = lastName;
p.Email = email;
p.Status = 'active';
if (await p.Save()) {
personId = p.ID;
}
else {
LogError(`Failed to create new person ${firstName} ${lastName} ${email}`)
}
}
// now call the base class to create the user, and pass in our LinkedRecordType and ID
return super.createNewUser(firstName, lastName, email, 'Other', pEntity?.ID, personId);
}
catch (e) {
LogError(`Error creating new user ${email} ${e}`);
return undefined;
}
}
}
API Documentation
Core Exports
The package exports numerous utilities and types:
Server Functions
serve(resolverPaths: string[], app?: Express, options?: MJServerOptions)
: Main server initializationcreateApp()
: Creates an Express application instance
Authentication
NewUserBase
: Base class for custom new user handlingTokenExpiredError
: Token expiration error classgetSystemUser(dataSource?: DataSource)
: Get system user for operations
Resolvers and Base Classes
ResolverBase
: Base class for custom resolversRunViewResolver
: Base resolver for view operationsPushStatusResolver
: Status update resolver base
Type Definitions
AppContext
: GraphQL context typeDataSourceInfo
: Database connection informationKeyValuePairInput
: Generic key-value input typeDeleteOptionsInput
: Delete operation options
Utility Functions
GetReadOnlyDataSource(dataSources: DataSourceInfo[])
: Get read-only data sourceGetReadWriteDataSource(dataSources: DataSourceInfo[])
: Get read-write data source
Entity Subclasses
The server includes specialized entity subclasses:
UserViewEntityServer
: Server-side user view handlingEntityPermissionsEntityServer
: Entity permission managementDuplicateRunEntityServer
: Duplicate detection operationsReportEntityServer
: Report generation and management
AI Integration
The server includes built-in AI capabilities:
Learning Cycle Scheduler
The server can automatically run AI learning cycles:
import { LearningCycleScheduler } from '@memberjunction/server/scheduler';
// In your server initialization
const scheduler = LearningCycleScheduler.Instance;
scheduler.setDataSources(dataSources);
scheduler.start(60); // Run every 60 minutes
AI Resolvers
RunAIPromptResolver
: Execute AI promptsAskSkipResolver
: Handle Skip AI queries
Security Configuration
Authentication Providers
The server supports multiple authentication providers:
Azure AD (MSAL):
- Set
TENANT_ID
andWEB_CLIENT_ID
environment variables - Supports Microsoft identity platform
- Set
Auth0:
- Set
AUTH0_DOMAIN
,AUTH0_CLIENT_ID
, andAUTH0_CLIENT_SECRET
- Supports Auth0 authentication
- Set
API Key Authentication
The server also supports API key authentication via the x-mj-api-key
header.
Access Control
For REST API access control, see the comprehensive documentation in REST_API.md.
Performance Optimization
Compression
The server includes built-in compression middleware:
- Responses larger than 1KB are compressed
- Binary files are excluded from compression
- Uses compression level 6 for optimal balance
Connection Pooling
Database connections are managed with TypeORM's connection pooling. Configure pool size in your TypeORM configuration.
Caching
The server uses LRU caching for frequently accessed data. Configure cache settings in your mj.config.cjs
:
databaseSettings: {
metadataCacheRefreshInterval: 300000 // 5 minutes
}
Advanced Configuration
Custom Directives
The server includes custom GraphQL directives:
@RequireSystemUser
: Requires system user permissions@Public
: Makes endpoints publicly accessible
WebSocket Configuration
For real-time subscriptions:
const webSocketServer = new WebSocketServer({
server: httpServer,
path: graphqlRootPath
});
Troubleshooting
Common Issues
- Authentication Errors: Ensure all required environment variables are set
- Database Connection: Verify connection string and credentials
- Module Loading: Check resolver paths are correct and accessible
- Transaction Errors: Review mutation logic for proper error handling
Debug Mode
Enable detailed logging by setting environment variables:
DEBUG=mj:*
NODE_ENV=development
Best Practices
- Use Read-Only Connections: Configure read-only database connections for query operations
- Implement Custom User Handling: Extend
NewUserBase
for organization-specific user creation - Monitor Performance: Use the built-in timing logs for transaction monitoring
- Secure Your REST API: Always configure entity and schema filters for production
- Handle Errors Gracefully: Implement proper error handling in custom resolvers
Contributing
When contributing to this package: 1. Follow the existing code style and patterns 2. Add appropriate TypeScript types 3. Include tests for new functionality 4. Update documentation as needed
License
ISC
8 months ago
4 months ago
8 months ago
6 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
6 months ago
6 months ago
5 months ago
8 months ago
8 months ago
8 months ago
6 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
5 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
9 months ago
5 months ago
9 months ago
9 months ago
9 months ago
9 months ago
8 months ago
7 months ago
5 months ago
9 months ago
9 months ago
5 months ago
5 months ago
8 months ago
8 months ago
7 months ago
9 months ago
5 months ago
5 months ago
8 months ago
8 months ago
5 months ago
5 months ago
10 months ago
5 months ago
8 months ago
4 months ago
10 months ago
6 months ago
11 months ago
10 months ago
11 months ago
6 months ago
11 months ago
4 months ago
8 months ago
8 months ago
12 months ago
6 months ago
6 months ago
8 months ago
12 months ago
12 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago