Resources Guide
Overview
Resources expose data that AI models can read. In v3.0, resources are defined using the @Resource decorator.
Basic Resource
import { ResourceDecorator as Resource, ExecutionContext } from 'nitrostack';
export class ProductResources {
@Resource({
uri: 'product://{id}',
name: 'Product Details',
description: 'Get detailed product information',
mimeType: 'application/json'
})
async getProduct(uri: string, context: ExecutionContext) {
// Extract ID from URI
const id = uri.split('://')[1];
const product = await this.productService.findById(id);
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(product, null, 2)
}]
};
}
}
@Resource Decorator
Options
interface ResourceOptions {
uri: string; // URI template (required)
name: string; // Resource name (required)
description: string; // What the resource provides (required)
mimeType?: string; // Content type (default: 'text/plain')
examples?: { // Example response
response?: any;
};
}
Complete Example
@Resource({
uri: 'user://{userId}/profile',
name: 'User Profile',
description: 'Get complete user profile with preferences and settings',
mimeType: 'application/json',
examples: {
response: {
id: 'user-123',
name: 'John Doe',
email: 'john@example.com',
preferences: {
theme: 'dark',
language: 'en'
}
}
}
})
async getUserProfile(uri: string, ctx: ExecutionContext) {
const userId = uri.match(/user:\/\/([^\/]+)/)[1];
const profile = await this.userService.getProfile(userId);
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(profile, null, 2)
}]
};
}
URI Templates
Simple URIs
@Resource({
uri: 'config://app',
name: 'App Configuration'
})
async getConfig(uri: string) {
return {
contents: [{
uri,
text: JSON.stringify({ version: '1.0.0' })
}]
};
}
Parameterized URIs
// Single parameter
@Resource({ uri: 'product://{id}' })
// Multiple parameters
@Resource({ uri: 'order://{orderId}/item/{itemId}' })
// Path parameters
@Resource({ uri: 'file:///{path}' })
Extracting Parameters
@Resource({ uri: 'order://{orderId}/item/{itemId}' })
async getOrderItem(uri: string) {
// Parse URI
const match = uri.match(/order:\/\/([^\/]+)\/item\/([^\/]+)/);
const orderId = match[1];
const itemId = match[2];
const item = await this.orderService.getItem(orderId, itemId);
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(item)
}]
};
}
Response Format
Contents Array
return {
contents: [
{
uri: 'product://123',
mimeType: 'application/json',
text: '{"id": "123", "name": "Product"}'
}
]
};
Multiple Contents
@Resource({ uri: 'report://{id}' })
async getReport(uri: string) {
const id = uri.split('://')[1];
const report = await this.reportService.generate(id);
return {
contents: [
{
uri: `${uri}/summary`,
mimeType: 'text/plain',
text: report.summary
},
{
uri: `${uri}/data`,
mimeType: 'application/json',
text: JSON.stringify(report.data)
},
{
uri: `${uri}/chart`,
mimeType: 'image/png',
blob: report.chartImage // Binary data
}
]
};
}
MIME Types
Common Types
// JSON data
mimeType: 'application/json'
// Plain text
mimeType: 'text/plain'
// HTML
mimeType: 'text/html'
// Markdown
mimeType: 'text/markdown'
// Images
mimeType: 'image/png'
mimeType: 'image/jpeg'
// CSV
mimeType: 'text/csv'
// XML
mimeType: 'application/xml'
Example with Different Types
export class DocumentResources {
@Resource({
uri: 'document://{id}',
name: 'Document',
mimeType: 'text/markdown'
})
async getDocument(uri: string) {
const id = uri.split('://')[1];
const doc = await this.docService.findById(id);
return {
contents: [{
uri,
mimeType: 'text/markdown',
text: doc.content
}]
};
}
@Resource({
uri: 'document://{id}/html',
name: 'Document HTML',
mimeType: 'text/html'
})
async getDocumentHtml(uri: string) {
const id = uri.match(/document:\/\/([^\/]+)/)[1];
const html = await this.docService.toHtml(id);
return {
contents: [{
uri,
mimeType: 'text/html',
text: html
}]
};
}
}
UI Widgets
Attach Widgets to Resources
@Resource({
uri: 'product://{id}',
name: 'Product Details',
examples: {
response: {
id: 'prod-1',
name: 'Awesome Product',
price: 99.99
}
}
})
@Widget('product-card') // ← Attach widget
async getProduct(uri: string) {
const id = uri.split('://')[1];
const product = await this.productService.findById(id);
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(product)
}]
};
}
Guards
Protected Resources
@Resource({ uri: 'user://{id}/private' })
@UseGuards(JWTGuard) // ← Auth required
async getPrivateData(uri: string, ctx: ExecutionContext) {
const requesterId = ctx.auth?.subject;
const userId = uri.match(/user:\/\/([^\/]+)/)[1];
// Only user can access their own data
if (requesterId !== userId) {
throw new Error('Unauthorized');
}
const data = await this.userService.getPrivateData(userId);
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(data)
}]
};
}
Caching
Cache Resource Responses
@Resource({ uri: 'config://app' })
@Cache({ ttl: 3600 }) // Cache for 1 hour
async getConfig(uri: string) {
const config = await this.configService.load();
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(config)
}]
};
}
Dependency Injection
Use Services
@Injectable()
export class ProductService {
async findById(id: string) {
// Database logic
}
}
export class ProductResources {
constructor(private productService: ProductService) {} // ← Injected
@Resource({ uri: 'product://{id}' })
async getProduct(uri: string) {
const id = uri.split('://')[1];
return {
contents: [{
uri,
text: JSON.stringify(await this.productService.findById(id))
}]
};
}
}
Examples
Configuration Resource
@Resource({
uri: 'config://app',
name: 'Application Configuration',
description: 'Current application settings and preferences',
mimeType: 'application/json'
})
@Cache({ ttl: 300 })
async getAppConfig(uri: string, ctx: ExecutionContext) {
const config = {
version: process.env.APP_VERSION || '1.0.0',
environment: process.env.NODE_ENV || 'development',
features: {
authentication: true,
widgets: true,
caching: true
},
limits: {
maxUploadSize: 10 * 1024 * 1024, // 10MB
rateLimit: 100
}
};
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(config, null, 2)
}]
};
}
Database Resource
@Resource({
uri: 'database://stats',
name: 'Database Statistics',
description: 'Current database size and performance metrics'
})
@UseGuards(AdminGuard)
async getDatabaseStats(uri: string) {
const stats = await this.db.getStats();
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify({
size: stats.size,
tables: stats.tableCount,
connections: stats.activeConnections,
queryTime: stats.avgQueryTime
}, null, 2)
}]
};
}
File Resource
@Resource({
uri: 'file:///{path}',
name: 'File Content',
description: 'Read file contents from the project',
mimeType: 'text/plain'
})
async getFile(uri: string) {
const path = uri.replace('file:///', '');
// Security: Validate path
if (path.includes('..') || path.startsWith('/')) {
throw new Error('Invalid path');
}
const content = await fs.readFile(path, 'utf-8');
return {
contents: [{
uri,
mimeType: 'text/plain',
text: content
}]
};
}
API Resource
@Resource({
uri: 'api://weather/{city}',
name: 'Weather Data',
description: 'Current weather from external API'
})
@Cache({ ttl: 600 }) // Cache for 10 minutes
async getWeather(uri: string) {
const city = uri.match(/weather\/([^\/]+)/)[1];
const response = await fetch(
`https://api.weather.com/v1/current?city=${city}&key=${process.env.WEATHER_API_KEY}`
);
const data = await response.json();
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(data)
}]
};
}
Best Practices
1. Use Descriptive URIs
// ✅ Good
'product://{id}'
'user://{userId}/profile'
'order://{orderId}/invoice'
// ❌ Avoid
'resource://{id}'
'data://{id}'
2. Set Correct MIME Types
// ✅ Good
mimeType: 'application/json' // For JSON
mimeType: 'text/markdown' // For Markdown
mimeType: 'text/html' // For HTML
// ❌ Avoid
mimeType: 'text/plain' // For everything
3. Cache When Appropriate
// ✅ Good - Static data
@Resource({ uri: 'config://app' })
@Cache({ ttl: 3600 })
// ✅ Good - Slow external API
@Resource({ uri: 'api://weather/{city}' })
@Cache({ ttl: 600 })
// ❌ Avoid - Real-time data
@Resource({ uri: 'stock://price/{symbol}' })
@Cache({ ttl: 3600 }) // Too long for stock prices
4. Validate URI Parameters
// ✅ Good
@Resource({ uri: 'user://{id}' })
async getUser(uri: string) {
const id = uri.split('://')[1];
if (!id || id.length < 3) {
throw new Error('Invalid user ID');
}
return {
contents: [{
uri,
text: JSON.stringify(await this.userService.findById(id))
}]
};
}
// ❌ Avoid - No validation
@Resource({ uri: 'user://{id}' })
async getUser(uri: string) {
const id = uri.split('://')[1];
return {
contents: [{
uri,
text: JSON.stringify(await this.userService.findById(id))
}]
};
}
5. Use Services
// ✅ Good - Reusable logic
export class UserResources {
constructor(private userService: UserService) {}
@Resource({ uri: 'user://{id}' })
async getUser(uri: string) {
const id = uri.split('://')[1];
return {
contents: [{
uri,
text: JSON.stringify(await this.userService.findById(id))
}]
};
}
}
// ❌ Avoid - Logic in resource
export class UserResources {
@Resource({ uri: 'user://{id}' })
async getUser(uri: string) {
const id = uri.split('://')[1];
const db = getDatabase();
const user = db.query('SELECT * FROM users WHERE id = ?', [id]);
return {
contents: [{ uri, text: JSON.stringify(user) }]
};
}
}
Next Steps
Pro Tip: Resources are great for exposing configuration, documentation, and API data that AI models can reference during conversations!