API Integration
Build a plugin that integrates with external APIs using shell services.
Overview#
This example shows how to build a plugin that fetches data from an external API, uses the shell's AI service for analysis, and stores results in the plugin database.
Using the API Client#
The SDK provides an authenticated API client that automatically includes auth headers:
| 1 | import { useApiClient } from '@naap/plugin-sdk'; |
| 2 | |
| 3 | function ExternalData() { |
| 4 | const client = useApiClient(); |
| 5 | |
| 6 | useEffect(() => { |
| 7 | // Calls go through the shell's API proxy |
| 8 | // /api/v1/my-plugin/external-data |
| 9 | client.get('/api/v1/my-plugin/external-data') |
| 10 | .then(setData); |
| 11 | }, []); |
| 12 | } |
Backend: External API Proxy#
Create a backend route that fetches from external services:
| 1 | // backend/src/routes/external.ts |
| 2 | import { Router } from 'express'; |
| 3 | |
| 4 | export const externalRoutes = Router(); |
| 5 | |
| 6 | externalRoutes.get('/external-data', async (req, res) => { |
| 7 | try { |
| 8 | const response = await fetch('https://api.example.com/data', { |
| 9 | headers: { |
| 10 | 'Authorization': `Bearer ${process.env.EXTERNAL_API_KEY}`, |
| 11 | 'Content-Type': 'application/json', |
| 12 | }, |
| 13 | }); |
| 14 | |
| 15 | if (!response.ok) { |
| 16 | throw new Error(`External API error: ${response.status}`); |
| 17 | } |
| 18 | |
| 19 | const data = await response.json(); |
| 20 | res.json(data); |
| 21 | } catch (error) { |
| 22 | console.error('[my-plugin] External API error:', error); |
| 23 | res.status(502).json({ |
| 24 | error: 'Failed to fetch external data', |
| 25 | }); |
| 26 | } |
| 27 | }); |
Using AI Integration#
The shell provides AI services that plugins can use:
| 1 | import { useIntegrations, useNotify } from '@naap/plugin-sdk'; |
| 2 | |
| 3 | function AIAnalyzer({ data }) { |
| 4 | const { ai } = useIntegrations(); |
| 5 | const notify = useNotify(); |
| 6 | const [analysis, setAnalysis] = useState(''); |
| 7 | const [loading, setLoading] = useState(false); |
| 8 | |
| 9 | const analyze = async () => { |
| 10 | setLoading(true); |
| 11 | try { |
| 12 | const result = await ai.complete({ |
| 13 | prompt: `Analyze this network data and provide insights:\n${JSON.stringify(data)}`, |
| 14 | model: 'gpt-4', |
| 15 | maxTokens: 500, |
| 16 | }); |
| 17 | setAnalysis(result.text); |
| 18 | } catch (err) { |
| 19 | notify.error('AI analysis failed'); |
| 20 | } finally { |
| 21 | setLoading(false); |
| 22 | } |
| 23 | }; |
| 24 | |
| 25 | return ( |
| 26 | <div className="p-4 border rounded-xl"> |
| 27 | <button |
| 28 | onClick={analyze} |
| 29 | disabled={loading} |
| 30 | className="px-4 py-2 bg-primary text-white rounded-lg" |
| 31 | > |
| 32 | {loading ? 'Analyzing...' : 'Analyze with AI'} |
| 33 | </button> |
| 34 | {analysis && ( |
| 35 | <div className="mt-4 p-4 bg-muted rounded-lg text-sm"> |
| 36 | {analysis} |
| 37 | </div> |
| 38 | )} |
| 39 | </div> |
| 40 | ); |
| 41 | } |
Using Storage Integration#
Upload and manage files through the shell's storage service:
| 1 | import { useStorage, useNotify } from '@naap/plugin-sdk'; |
| 2 | |
| 3 | function FileUploader() { |
| 4 | const storage = useStorage(); |
| 5 | const notify = useNotify(); |
| 6 | const [uploading, setUploading] = useState(false); |
| 7 | |
| 8 | const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { |
| 9 | const file = e.target.files?.[0]; |
| 10 | if (!file) return; |
| 11 | |
| 12 | setUploading(true); |
| 13 | try { |
| 14 | const url = await storage.upload(file, { |
| 15 | path: `reports/${file.name}`, |
| 16 | }); |
| 17 | notify.success(`Uploaded! URL: ${url}`); |
| 18 | } catch { |
| 19 | notify.error('Upload failed'); |
| 20 | } finally { |
| 21 | setUploading(false); |
| 22 | } |
| 23 | }; |
| 24 | |
| 25 | return ( |
| 26 | <label className="cursor-pointer px-4 py-2 border rounded-lg hover:bg-muted"> |
| 27 | {uploading ? 'Uploading...' : 'Upload File'} |
| 28 | <input type="file" className="hidden" onChange={handleUpload} /> |
| 29 | </label> |
| 30 | ); |
| 31 | } |
Webhook Pattern#
Handle incoming webhooks from external services:
| 1 | // backend/src/routes/webhooks.ts |
| 2 | import { Router } from 'express'; |
| 3 | import crypto from 'crypto'; |
| 4 | |
| 5 | export const webhookRoutes = Router(); |
| 6 | |
| 7 | webhookRoutes.post('/webhooks/github', async (req, res) => { |
| 8 | // Verify webhook signature |
| 9 | const signature = req.headers['x-hub-signature-256'] as string; |
| 10 | const expected = `sha256=${crypto |
| 11 | .createHmac('sha256', process.env.WEBHOOK_SECRET!) |
| 12 | .update(JSON.stringify(req.body)) |
| 13 | .digest('hex')}`; |
| 14 | |
| 15 | if (signature !== expected) { |
| 16 | return res.status(401).json({ error: 'Invalid signature' }); |
| 17 | } |
| 18 | |
| 19 | // Process the webhook |
| 20 | const event = req.headers['x-github-event']; |
| 21 | console.log(`Received GitHub event: ${event}`); |
| 22 | |
| 23 | // Store or process the data |
| 24 | // ... |
| 25 | |
| 26 | res.json({ received: true }); |
| 27 | }); |
Reusable External API Proxy#
For plugins that need to call third-party APIs directly from the frontend, use the built-in createExternalProxy middleware. It handles CORS, SSRF protection, error forwarding, and timeouts automatically:
| 1 | // backend/src/server.ts |
| 2 | import { createPluginServer, createExternalProxy } from '@naap/plugin-server-sdk'; |
| 3 | |
| 4 | const { router, start } = createPluginServer({ name: 'my-plugin', port: 4020 }); |
| 5 | |
| 6 | router.post( |
| 7 | '/my-prefix/api-proxy', |
| 8 | ...createExternalProxy({ |
| 9 | allowedHosts: ['api.example.com'], |
| 10 | targetUrlHeader: 'X-Target-URL', |
| 11 | forwardHeaders: { |
| 12 | 'Authorization': `Bearer ${process.env.EXTERNAL_API_KEY}`, |
| 13 | }, |
| 14 | }) |
| 15 | ); |
| 1 | // frontend/src/lib/api.ts |
| 2 | import { getPluginBackendUrl } from '@naap/plugin-sdk'; |
| 3 | |
| 4 | const proxyUrl = getPluginBackendUrl('my-plugin', { |
| 5 | apiPath: '/api/v1/my-prefix/api-proxy', |
| 6 | }); |
| 7 | |
| 8 | const res = await fetch(proxyUrl, { |
| 9 | method: 'POST', |
| 10 | headers: { |
| 11 | 'Content-Type': 'application/json', |
| 12 | 'X-Target-URL': 'https://api.example.com/data', |
| 13 | }, |
| 14 | body: JSON.stringify({ query: 'test' }), |
| 15 | }); |
See the External API Proxy Guide and full API reference for detailed configuration options.
Best Practices#
- Proxy external calls through backend -- Use
createExternalProxyfrom@naap/plugin-server-sdkinstead of writing custom proxy logic - Handle errors gracefully -- Show user-friendly messages, log details server-side
- Cache responses -- Use in-memory or database caching for frequently accessed data
- Rate limit -- Respect external API rate limits with backoff strategies
- Use environment variables -- Store API keys and secrets in
.env, never hardcode - Whitelist specific hosts -- Always set
allowedHoststo prevent SSRF attacks