Developer Platform & Node SDK
The Easy AppServer SDK provides a comprehensive development environment for building applications with TypeScript support, manifest builders, lifecycle hooks, and client helpers for all platform services.
Overview
The @easy/appserver-sdk package abstracts the complexity of platform integration, providing:
- Manifest builder with type safety
- Lifecycle hook orchestration
- gRPC client generation and helpers
- GraphQL client utilities
- Request signing and authentication
- Development tooling
Package: web/v2/packages/appserver-sdk
SDK Architecture
Core Components
@easy/appserver-sdk/
├─ manifest/ # Manifest builder API
├─ runtime/ # Lifecycle controller & state machine
├─ http/ # HTTP server & request helpers
├─ config/ # Configuration loader
├─ database/ # Database client
└─ utils/ # Logger, certificates, retry logic
Manifest Builder
Code Reference: web/v2/packages/appserver-sdk/src/manifest/builder.ts:1
Fluent API
import { defineManifest } from '@easy/appserver-sdk';
const manifest = defineManifest()
.name('de.easy-m.todos')
.certificate(certificatePEM)
// Web app configuration
.ui(ui => ui
.mode('FEDERATION') // or 'WEB_COMPONENT', 'ESM_COMPONENT'
.entryAsset('remoteEntry.js')
.exposedModule('./App')
.routeBase('/apps/todos')
)
// Navigation
.navigation({
path: '/todos',
label: 'Todos',
icon: 'list-icon'
})
.navigation({
path: '/settings',
label: 'Settings',
icon: 'cog-icon'
})
// API configuration
.api({
basePath: '/api',
upstreamBaseURL: 'http://localhost:3000'
})
// Permissions
.requirePermission('read_users_any')
.requirePermission('trigger_hook_user_created')
.requirePermission('listen_hook_user_updated')
.requirePermission('execute_activity_send_email')
// Dependencies
.dependency({ appName: 'de.easy-m.auth', required: true, minVersion: '^2.0.0' })
.dependency({ appName: 'de.easy-m.analytics', required: false, minVersion: '^1.0.0' })
// Settings
.setting({
key: 'api_key',
type: 'string!',
sensitive: true,
validation: {
pattern: '^sk_[a-z]+_[A-Za-z0-9]{32}$'
}
})
.setting({
key: 'max_items',
type: 'int',
default: 100,
validation: { min: 1, max: 1000 }
})
.build();
Asset Collection
Automatically collect and bundle assets:
import { collectViteAssets, defineManifest } from '@easy/appserver-sdk';
const builder = defineManifest()
.name('de.easy-m.todos')
.certificate(certificatePEM);
// Collect from Vite build output
const result = await collectViteAssets({
projectDir: '.',
privateKey: privateKeyPEM,
distDir: 'dist',
skipBuild: false
});
// Add assets to manifest
result.assets.forEach(asset => {
builder.asset(asset);
});
const manifest = builder.build();
Features:
- Automatic MIME type detection
- SHA-256 checksum calculation
- Base64 encoding for binary assets
- Size validation
Lifecycle Controller
Code Reference: web/v2/packages/appserver-sdk/src/runtime/lifecycle.ts:29
Hook Execution
The SDK orchestrates lifecycle hooks in the correct order:
import { createApp } from '@easy/appserver-sdk';
await createApp({
app: {
// Lifecycle hooks
async onCreate(ctx) {
console.log('App created - initialize resources');
await initializeDatabase();
},
async onRegister(ctx) {
console.log('App registered - setup complete');
},
async onInstall(ctx) {
console.log('App installed - perform setup');
await seedData();
await registerScheduledJobs();
},
async onUninstall(ctx) {
console.log('App uninstalling - cleanup');
await cleanupData();
await unregisterJobs();
},
async onDestroy(ctx) {
console.log('App destroyed - final teardown');
await closeConnections();
}
},
appServerConfig: {
identity: {
name: 'de.easy-m.todos',
privateKeyPath: './certs/key.pem'
},
graphql: {
httpEndpoint: 'http://localhost:8080/graphql',
wsEndpoint: 'ws://localhost:8080/graphql'
}
}
});
Hook Order
Lifecycle Flow:
1. onCreate() - App initialization (once)
2. onRegister() - Registration with platform
3. onInstall() - Installation setup (per instance)
↓
[App Running]
↓
4. onUninstall() - Cleanup before removal
5. onDestroy() - Final teardown (once)
State Synchronization
Code Reference: web/v2/packages/appserver-sdk/src/runtime/state-machine.ts:113
The SDK mirrors the platform's state machine and automatically syncs state transitions. State changes trigger the corresponding lifecycle hooks (onCreate, onRegister, onInstall, etc.) automatically based on events from the AppServer.
Hook Idempotency
Hooks should be idempotent (safe to run multiple times):
let installed = false;
async function onInstall() {
if (installed) {
console.log('Already installed, skipping');
return;
}
await performSetup();
installed = true;
}
Working with AppServer Services
gRPC Clients
Generated TypeScript clients are available in the @easy/client-ts package (separate from the SDK):
import { createMarketplaceServiceClient } from '@easy/client-ts';
// Create client with credentials
const marketplace = createMarketplaceServiceClient({
address: 'localhost:50051',
appName: 'de.easy-m.todos',
privateKeyPath: './certs/key.pem'
});
// Register app
await marketplace.registerApp({ manifest });
// List installed apps
const apps = await marketplace.listApps({});
For full gRPC service documentation, see gRPC Services.
GraphQL Queries
Use standard GraphQL clients like Apollo or urql. The SDK provides authenticated appFetch for requests:
import { appFetch } from '@easy/appserver-sdk';
// Query with appFetch
const response = await appFetch('http://localhost:8080/graphql', {
method: 'POST',
appName: 'de.easy-m.todos',
privateKeyPath: './certs/key.pem',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: `
query GetApps {
apps {
name
state
}
}
`
})
});
const { data } = await response.json();
For GraphQL API documentation, see GraphQL API & Subscriptions.
Authentication Helpers
Certificate Management
import { loadCertificate, loadPrivateKey } from '@easy/appserver-sdk';
// Load certificate and private key
const cert = await loadCertificate('./cert.pem');
const key = await loadPrivateKey('./key.pem');
Certificates should be generated using standard tools like OpenSSL. See Authentication & Authorization for certificate generation instructions.
Request Signing
Code Reference: web/v2/packages/appserver-sdk/src/http/app-fetch.ts:34-128
The SDK provides appFetch for making authenticated requests to other apps:
import { appFetch } from '@easy/appserver-sdk';
// Authenticated inter-app request
const response = await appFetch('http://another-app/api/data', {
method: 'POST',
body: JSON.stringify({ key: 'value' }),
appName: 'de.easy-m.todos',
privateKeyPath: './certs/key.pem',
headers: {
'Content-Type': 'application/json'
}
});
// Behind the scenes:
// 1. Create signature payload: METHOD\n/path?query\nTIMESTAMP\nAPPNAME
// 2. Sign with RSA-SHA256 private key
// 3. Add metadata headers:
// X-App-Name: de.easy-m.todos
// X-App-Timestamp: 2025-01-24T15:30:00Z (RFC3339 format)
// X-App-Signature: base64-encoded-rsa-signature
The signature format matches AppServer's authenticator expectations in pkg/v2/presentation/grpc/grpcauth/authenticator.go:89-155.
Working with Hooks and Activities
Hooks and activities are managed through gRPC services. Use the @easy/client-ts package to interact with them:
import { createHooksServiceClient } from '@easy/client-ts';
const hooksClient = createHooksServiceClient({
address: 'localhost:50051',
appName: 'de.easy-m.todos',
privateKeyPath: './certs/key.pem'
});
// Trigger a hook
await hooksClient.trigger({
hookName: 'user.created',
payload: { userId: '123', email: 'user@example.com' }
});
For detailed information on hooks and activities, see:
Settings Management
Settings are defined in the manifest and accessed through GraphQL or gRPC:
// Define in manifest
.setting({
key: 'api_key',
type: 'string!',
sensitive: true
})
.setting({
key: 'max_items',
type: 'int',
default: 100
})
Access settings at runtime via GraphQL or the settings service client from @easy/client-ts.
For detailed information, see Settings Management.
Development Tooling
Local Development
# Watch and rebuild SDK on changes
npm run dev
The npm run dev script runs tsup --watch (web/v2/packages/appserver-sdk/package.json:15-24), which only watches and rebuilds the SDK itself. It does not:
- Auto-connect your application to the platform
- Reload manifests automatically
- Start your application process
To develop an application:
- Run
npm run devin the SDK package (optional, for SDK development) - Start your application process separately using
createApp()from the SDK - Your application connects to the platform when it calls lifecycle hooks
The SDK provides building blocks; you must implement your own development server and hot-reload logic for your application.
TypeScript Support
Full type safety with SDK types:
import type { AppContext, SDKOptions } from '@easy/appserver-sdk';
// Context is typed with your config
interface MyConfig {
apiKey: string;
maxRetries: number;
}
const app: SDKOptions<MyConfig> = {
app: {
async onCreate(ctx: AppContext<MyConfig>) {
// ctx.config is typed as MyConfig
console.log(ctx.config.apiKey);
}
},
appServerConfig: {
// ...
}
};
Best Practices
Error Handling
async function onInstall() {
try {
await setupDatabase();
} catch (err) {
console.error('Setup failed:', err);
throw err; // Platform marks installation as failed
}
}
Graceful Shutdown
// The SDK handles graceful shutdown automatically
// SIGTERM/SIGINT signals trigger the onDestroy hook
process.on('SIGTERM', () => {
console.log('Received SIGTERM, SDK will handle cleanup');
// SDK automatically calls onDestroy() before exit
});
Logging
import { createLogger } from '@easy/appserver-sdk';
const logger = createLogger({
appName: 'de.easy-m.todos',
level: 'info'
});
logger.info('Processing request', { userId: '123' });
logger.error('Failed to save', { error: err });
Testing
Test your app by running it locally against a development AppServer instance. Use standard testing frameworks like Jest or Vitest:
import { describe, it, expect } from 'vitest';
describe('App lifecycle', () => {
it('should initialize database onCreate', async () => {
// Test your lifecycle hooks with mocked dependencies
const mockDb = createMockDatabase();
await onCreate({ db: mockDb });
expect(mockDb.connected).toBe(true);
});
});
Related Concepts
- Application Manifest - Manifest structure
- Application Lifecycle - Hook execution flow
- gRPC Services - Service APIs
- GraphQL API & Subscriptions - GraphQL client
- Hooks Architecture - Hook helpers
- Activities & Background Workflows - Activity helpers
- Settings Management - Settings helpers
Further Reading
- Getting Started: Building Apps - SDK tutorial
- Node.js SDK Documentation - Complete SDK documentation