2.48.0 • Published 4 months ago

@memberjunction/server v2.48.0

Weekly downloads
-
License
ISC
Repository
-
Last release
4 months ago

@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 variableDescription
DB_HOSTThe hostname for the common data store database
DB_PORTThe port for the common data store database (default 1433)
DB_USERNAMEThe username used to authenticate with the common data store
DB_PASSWORDThe password used to authenticate with the common data store
DB_DATABASEThe common data store database name
PORTThe port used by the server (default 4000)
ROOT_PATHThe GraphQL root path (default /)
WEB_CLIENT_IDThe client ID used for MSAL authentication
TENANT_IDThe tenant ID used for MSAL authentication
ENABLE_INTROSPECTIONA flag to allow GraphQL introspection (default false)
WEBSITE_RUN_FROM_PACKAGEAn Azure flag to indicate a read-only file system
AUTH0_DOMAINThe Auth0 domain
AUTH0_CLIENT_IDThe Auth0 Client ID
AUTH0_CLIENT_SECRETThe Auth0 Client secret
MJ_CORE_SCHEMAThe core schema to use for the data provider
CONFIG_FILEAn absolute path to the config file json
DB_READ_ONLY_USERNAMEUsername for read-only database connection (optional)
DB_READ_ONLY_PASSWORDPassword 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 initialization
  • createApp(): Creates an Express application instance

Authentication

  • NewUserBase: Base class for custom new user handling
  • TokenExpiredError: Token expiration error class
  • getSystemUser(dataSource?: DataSource): Get system user for operations

Resolvers and Base Classes

  • ResolverBase: Base class for custom resolvers
  • RunViewResolver: Base resolver for view operations
  • PushStatusResolver: Status update resolver base

Type Definitions

  • AppContext: GraphQL context type
  • DataSourceInfo: Database connection information
  • KeyValuePairInput: Generic key-value input type
  • DeleteOptionsInput: Delete operation options

Utility Functions

  • GetReadOnlyDataSource(dataSources: DataSourceInfo[]): Get read-only data source
  • GetReadWriteDataSource(dataSources: DataSourceInfo[]): Get read-write data source

Entity Subclasses

The server includes specialized entity subclasses:

  • UserViewEntityServer: Server-side user view handling
  • EntityPermissionsEntityServer: Entity permission management
  • DuplicateRunEntityServer: Duplicate detection operations
  • ReportEntityServer: 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 prompts
  • AskSkipResolver: Handle Skip AI queries

Security Configuration

Authentication Providers

The server supports multiple authentication providers:

  1. Azure AD (MSAL):

    • Set TENANT_ID and WEB_CLIENT_ID environment variables
    • Supports Microsoft identity platform
  2. Auth0:

    • Set AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET
    • Supports Auth0 authentication

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

  1. Authentication Errors: Ensure all required environment variables are set
  2. Database Connection: Verify connection string and credentials
  3. Module Loading: Check resolver paths are correct and accessible
  4. 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

  1. Use Read-Only Connections: Configure read-only database connections for query operations
  2. Implement Custom User Handling: Extend NewUserBase for organization-specific user creation
  3. Monitor Performance: Use the built-in timing logs for transaction monitoring
  4. Secure Your REST API: Always configure entity and schema filters for production
  5. 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

2.23.2

8 months ago

2.46.0

4 months ago

2.23.1

8 months ago

2.34.0

6 months ago

2.19.4

9 months ago

2.19.5

9 months ago

2.19.2

9 months ago

2.19.3

9 months ago

2.19.0

9 months ago

2.19.1

9 months ago

2.34.2

6 months ago

2.34.1

6 months ago

2.45.0

5 months ago

2.22.1

8 months ago

2.22.0

8 months ago

2.22.2

8 months ago

2.33.0

6 months ago

2.18.3

9 months ago

2.18.1

9 months ago

2.18.2

9 months ago

2.18.0

9 months ago

2.21.0

9 months ago

2.44.0

5 months ago

2.29.0

7 months ago

2.29.2

7 months ago

2.29.1

7 months ago

2.32.0

7 months ago

2.32.2

7 months ago

2.32.1

7 months ago

2.17.0

9 months ago

2.43.0

5 months ago

2.20.2

9 months ago

2.20.3

9 months ago

2.20.0

9 months ago

2.20.1

9 months ago

2.28.0

8 months ago

2.31.0

7 months ago

2.39.0

5 months ago

2.16.1

9 months ago

2.16.0

9 months ago

2.42.1

5 months ago

2.42.0

5 months ago

2.27.1

8 months ago

2.27.0

8 months ago

2.30.0

7 months ago

2.15.2

9 months ago

2.38.0

5 months ago

2.41.0

5 months ago

2.26.1

8 months ago

2.26.0

8 months ago

2.37.1

5 months ago

2.37.0

5 months ago

2.14.0

10 months ago

2.40.0

5 months ago

2.25.0

8 months ago

2.48.0

4 months ago

2.13.4

10 months ago

2.36.0

6 months ago

2.13.2

11 months ago

2.13.3

10 months ago

2.13.0

11 months ago

2.36.1

6 months ago

2.13.1

11 months ago

2.47.0

4 months ago

2.24.1

8 months ago

2.24.0

8 months ago

2.12.0

12 months ago

2.35.1

6 months ago

2.35.0

6 months ago

2.23.0

8 months ago

2.11.0

12 months ago

2.10.0

12 months ago

2.9.0

12 months ago

2.8.0

1 year ago

2.7.0

1 year ago

2.7.1

1 year ago

2.6.1

1 year ago

2.6.0

1 year ago

2.5.2

1 year ago

1.6.1

1 year ago

1.6.0

1 year ago

2.4.1

1 year ago

2.4.0

1 year ago

1.5.3

1 year ago

1.5.2

1 year ago

1.5.1

1 year ago

1.5.0

1 year ago

2.3.0

1 year ago

2.3.2

1 year ago

2.3.1

1 year ago

2.3.3

1 year ago

1.4.1

1 year ago

1.4.0

1 year ago

2.2.1

1 year ago

2.2.0

1 year ago

1.3.3

1 year ago

1.3.2

1 year ago

1.3.1

1 year ago

1.3.0

1 year ago

2.1.2

1 year ago

2.1.1

1 year ago

2.1.4

1 year ago

2.1.3

1 year ago

2.1.5

1 year ago

2.0.0

1 year ago

1.8.1

1 year ago

1.8.0

1 year ago

1.7.1

1 year ago

1.7.0

1 year ago

2.5.0

1 year ago

2.5.1

1 year ago

1.2.2

1 year ago

1.2.1

1 year ago

1.2.0

1 year ago

1.1.1

1 year ago

1.1.0

1 year ago

1.1.3

1 year ago

1.1.2

1 year ago

1.0.11

1 year ago

1.0.9

2 years ago

1.0.7-next.0

2 years ago

1.0.8

2 years ago

1.0.7

2 years ago

1.0.8-next.6

2 years ago

1.0.8-next.5

2 years ago

1.0.8-next.4

2 years ago

1.0.8-next.3

2 years ago

1.0.8-next.2

2 years ago

1.0.8-next.1

2 years ago

1.0.8-next.0

2 years ago

1.0.8-beta.0

2 years ago

1.0.6

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago

0.9.264

2 years ago

0.9.263

2 years ago

0.9.265

2 years ago

0.9.261

2 years ago

0.9.260

2 years ago

0.9.259

2 years ago

0.9.258

2 years ago

0.9.255

2 years ago

0.9.254

2 years ago

0.9.257

2 years ago

0.9.256

2 years ago

0.9.253

2 years ago

0.9.252

2 years ago

0.9.251

2 years ago

0.9.250

2 years ago

0.9.248

2 years ago

0.9.246

2 years ago

0.9.247

2 years ago

0.9.244

2 years ago

0.9.243

2 years ago

0.9.245

2 years ago

0.9.241

2 years ago

0.9.240

2 years ago

0.9.242

2 years ago

0.9.238

2 years ago

0.9.237

2 years ago

0.9.236

2 years ago

0.9.231

2 years ago

0.9.230

2 years ago

0.9.233

2 years ago

0.9.232

2 years ago

0.9.229

2 years ago

0.9.235

2 years ago

0.9.234

2 years ago

0.9.228

2 years ago

0.9.227

2 years ago

0.9.226

2 years ago

0.9.225

2 years ago

0.9.222

2 years ago

0.9.221

2 years ago

0.9.224

2 years ago

0.9.223

2 years ago

0.9.220

2 years ago

0.9.219

2 years ago

0.9.217

2 years ago

0.9.216

2 years ago

0.9.218

2 years ago

0.9.211

2 years ago

0.9.210

2 years ago

0.9.213

2 years ago

0.9.212

2 years ago

0.9.215

2 years ago

0.9.214

2 years ago

0.9.209

2 years ago

0.9.206

2 years ago

0.9.205

2 years ago

0.9.208

2 years ago

0.9.207

2 years ago

0.9.204

2 years ago

0.9.200

2 years ago

0.9.202

2 years ago

0.9.198

2 years ago

0.9.199

2 years ago

0.9.197

2 years ago

0.9.196

2 years ago

0.9.187

2 years ago

0.9.186

2 years ago

0.9.189

2 years ago

0.9.188

2 years ago

0.9.183

2 years ago

0.9.185

2 years ago

0.9.194

2 years ago

0.9.193

2 years ago

0.9.190

2 years ago

0.9.192

2 years ago

0.9.191

2 years ago

0.9.182

2 years ago

0.9.181

2 years ago

0.9.180

2 years ago

0.9.179

2 years ago

0.9.176

2 years ago

0.9.175

2 years ago

0.9.178

2 years ago

0.9.172

2 years ago

0.9.174

2 years ago

0.9.173

2 years ago

0.9.170

2 years ago

0.9.169

2 years ago

0.9.166

2 years ago

0.9.168

2 years ago

0.9.165

2 years ago

0.9.164

2 years ago

0.9.163

2 years ago

0.9.162

2 years ago

0.9.161

2 years ago

0.9.160

2 years ago

0.9.154

2 years ago

0.9.153

2 years ago

0.9.155

2 years ago

0.9.158

2 years ago

0.9.157

2 years ago

0.9.159

2 years ago

0.9.151

2 years ago

0.9.142

2 years ago

0.9.141

2 years ago

0.9.129

2 years ago

0.9.128

2 years ago

0.9.125

2 years ago

0.9.124

2 years ago

0.9.110

2 years ago

0.9.112

2 years ago

0.9.111

2 years ago

0.9.114

2 years ago

0.9.113

2 years ago

0.9.115

2 years ago

0.9.101

2 years ago

0.9.100

2 years ago

0.9.96

2 years ago

0.9.97

2 years ago

0.9.98

2 years ago

0.9.99

2 years ago

0.9.107

2 years ago

0.9.106

2 years ago

0.9.93

2 years ago

0.9.109

2 years ago

0.9.94

2 years ago

0.9.108

2 years ago

0.9.95

2 years ago

0.9.103

2 years ago

0.9.102

2 years ago

0.9.105

2 years ago

0.9.104

2 years ago

0.9.90

2 years ago

0.9.89

2 years ago

0.9.85

2 years ago

0.9.87

2 years ago

0.9.88

2 years ago

0.9.84

2 years ago

0.9.81

2 years ago

0.9.82

2 years ago

0.9.83

2 years ago

0.9.80

2 years ago

0.9.78

2 years ago

0.9.79

2 years ago

0.9.77

2 years ago

0.9.76

2 years ago

0.9.75

2 years ago

0.9.74

2 years ago

0.9.72

2 years ago

0.9.73

2 years ago

0.9.70

2 years ago

0.9.71

2 years ago

0.9.67

2 years ago

0.9.68

2 years ago

0.9.69

2 years ago

0.9.65

2 years ago

0.9.66

2 years ago

0.9.64

2 years ago

0.9.63

2 years ago

0.9.61

2 years ago

0.9.62

2 years ago

0.9.58

2 years ago

0.9.59

2 years ago

0.9.60

2 years ago

0.9.57

2 years ago

0.9.56

2 years ago

0.9.55

2 years ago

0.9.54

2 years ago

0.9.53

2 years ago

0.9.52

2 years ago

0.9.51

2 years ago

0.9.50

2 years ago

0.9.49

2 years ago

0.9.48

2 years ago

0.9.47

2 years ago

0.9.46

2 years ago

0.9.45

2 years ago

0.9.44

2 years ago

0.9.43

2 years ago

0.9.42

2 years ago

0.9.41

2 years ago

0.9.40

2 years ago

0.9.39

2 years ago

0.9.38

2 years ago

0.9.37

2 years ago

0.9.36

2 years ago

0.9.35

2 years ago

0.9.34

2 years ago

0.9.32

2 years ago

0.9.31

2 years ago

0.9.30

2 years ago

0.9.28

2 years ago

0.9.27

2 years ago

0.9.26

2 years ago

0.9.25

2 years ago

0.9.24

2 years ago

0.9.23

2 years ago

0.9.22

2 years ago

0.9.21

2 years ago

0.9.20

2 years ago

0.9.19

2 years ago

0.9.16

2 years ago

0.9.15

2 years ago

0.9.14

2 years ago

0.9.13

2 years ago

0.9.12

2 years ago

0.9.11

2 years ago

0.9.10

2 years ago

0.9.9

2 years ago

0.9.8

2 years ago

0.9.7

2 years ago

0.9.6

2 years ago

0.9.5

2 years ago

0.9.4

2 years ago

0.9.3

2 years ago

0.9.2

2 years ago

0.9.1

2 years ago

0.9.0

2 years ago