REST API
Create custom API endpoints for your plugins.
Registering Endpoints
sdk.rest.register('GET', '/api/my-plugin/items', async (req, res) => {
const items = await sdk.storage.get('items') || [];
return res.json({
success: true,
data: items
});
});HTTP Methods
// GET - Retrieve data
sdk.rest.register('GET', '/api/items', handler);
// POST - Create data
sdk.rest.register('POST', '/api/items', handler);
// PUT - Update data
sdk.rest.register('PUT', '/api/items/:id', handler);
// PATCH - Partial update
sdk.rest.register('PATCH', '/api/items/:id', handler);
// DELETE - Remove data
sdk.rest.register('DELETE', '/api/items/:id', handler);Request Object
sdk.rest.register('POST', '/api/items/:id', async (req, res) => {
// URL parameters
req.params.id // From :id in URL
// Query string (?page=1&search=hello)
req.query.page // '1'
req.query.search // 'hello'
// Request body (JSON)
req.body.name // Posted data
req.body.value
// Headers
req.headers['content-type']
req.headers.authorization
// Current user (if authenticated)
req.user // { id, email, role, ... }
// Files (multipart uploads)
req.files // Array of uploaded files
});Response Methods
sdk.rest.register('GET', '/api/example', async (req, res) => {
// JSON response
return res.json({ success: true, data: {} });
// With status code
return res.status(201).json({ created: true });
// Error responses
return res.status(400).json({ error: 'Bad request' });
return res.status(401).json({ error: 'Unauthorized' });
return res.status(404).json({ error: 'Not found' });
return res.status(500).json({ error: 'Server error' });
// Plain text
return res.send('OK');
// HTML
return res.send('<h1>Hello</h1>');
// Redirect
return res.redirect('/other-url');
// Set headers
res.setHeader('X-Custom', 'value');
return res.json({ data: true });
// Set cookie
res.cookie('session', 'abc123', { maxAge: 86400000 });
return res.json({ success: true });
});Authentication
Require Login
sdk.rest.register('GET', '/api/user/profile', async (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
return res.json({ user: req.user });
}, {
auth: true
});Require Admin Role
sdk.rest.register('POST', '/api/admin/action', async (req, res) => {
// Handler only runs if user is admin
return res.json({ success: true });
}, {
auth: true,
roles: ['admin']
});Validation
sdk.rest.register('POST', '/api/contact', async (req, res) => {
const { name, email, message } = req.body;
const errors = [];
if (!name || name.length < 2) {
errors.push({ field: 'name', message: 'Name required' });
}
if (!email || !email.includes('@')) {
errors.push({ field: 'email', message: 'Valid email required' });
}
if (!message || message.length < 10) {
errors.push({ field: 'message', message: 'Message too short' });
}
if (errors.length > 0) {
return res.status(400).json({ errors });
}
// Process valid data...
return res.json({ success: true });
});File Uploads
sdk.rest.register('POST', '/api/upload', async (req, res) => {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ error: 'No file uploaded' });
}
const file = req.files[0];
// Validate type
if (!file.mimetype.startsWith('image/')) {
return res.status(400).json({ error: 'Only images allowed' });
}
// Validate size (5MB)
if (file.size > 5 * 1024 * 1024) {
return res.status(400).json({ error: 'File too large' });
}
// Save file
const result = await sdk.media.upload(file, {
folder: 'my-plugin/uploads'
});
return res.json({
success: true,
url: result.url
});
});Rate Limiting
sdk.rest.register('POST', '/api/send', async (req, res) => {
// Handler
}, {
rateLimit: {
max: 10, // Max requests
window: 60000 // Per minute
}
});Complete CRUD Example
module.exports = async function(sdk) {
const COLLECTION = 'notes';
// List all notes
sdk.rest.register('GET', '/api/notes', async (req, res) => {
const { page = 1, limit = 20 } = req.query;
let notes = await sdk.storage.get(COLLECTION) || [];
// Sort by date
notes.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
// Paginate
const start = (page - 1) * limit;
const items = notes.slice(start, start + parseInt(limit));
return res.json({
items,
total: notes.length,
page: parseInt(page),
pages: Math.ceil(notes.length / limit)
});
});
// Get single note
sdk.rest.register('GET', '/api/notes/:id', async (req, res) => {
const notes = await sdk.storage.get(COLLECTION) || [];
const note = notes.find(n => n.id === req.params.id);
if (!note) {
return res.status(404).json({ error: 'Note not found' });
}
return res.json(note);
});
// Create note
sdk.rest.register('POST', '/api/notes', async (req, res) => {
const { title, content } = req.body;
if (!title) {
return res.status(400).json({ error: 'Title required' });
}
const notes = await sdk.storage.get(COLLECTION) || [];
const note = {
id: sdk.utils.generateId(),
title,
content: content || '',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
notes.push(note);
await sdk.storage.set(COLLECTION, notes);
return res.status(201).json(note);
}, { auth: true });
// Update note
sdk.rest.register('PUT', '/api/notes/:id', async (req, res) => {
const { title, content } = req.body;
const notes = await sdk.storage.get(COLLECTION) || [];
const index = notes.findIndex(n => n.id === req.params.id);
if (index === -1) {
return res.status(404).json({ error: 'Note not found' });
}
notes[index] = {
...notes[index],
...(title !== undefined && { title }),
...(content !== undefined && { content }),
updatedAt: new Date().toISOString()
};
await sdk.storage.set(COLLECTION, notes);
return res.json(notes[index]);
}, { auth: true });
// Delete note
sdk.rest.register('DELETE', '/api/notes/:id', async (req, res) => {
let notes = await sdk.storage.get(COLLECTION) || [];
const initialLength = notes.length;
notes = notes.filter(n => n.id !== req.params.id);
if (notes.length === initialLength) {
return res.status(404).json({ error: 'Note not found' });
}
await sdk.storage.set(COLLECTION, notes);
return res.json({ success: true });
}, { auth: true });
return {};
};Using from Frontend
// List
const res = await fetch('/api/notes');
const data = await res.json();
// Create
await fetch('/api/notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'New Note', content: 'Content here' })
});
// Update
await fetch('/api/notes/abc123', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Updated Title' })
});
// Delete
await fetch('/api/notes/abc123', {
method: 'DELETE'
});