Quick Start - Node.js

Get started with Importly in your Node.js application with server-side processing and webhook handling.

Setup

Install the required dependencies:

bash
1npm install axios express

1. Get Your API Key

  1. Create an Account at Importly.io
  2. Find your API Key via the api key page
  3. Set it as an environment variable:
bash
1# .env
2IMPORTLY_API_KEY=your_api_key_here
3PORT=3000
4WEBHOOK_URL=https://your-domain.com/webhook/importly
5IMPORTLY_API_URL=https://api.importly.io

2. Create Importly Client

Create a robust Importly client with error handling:

javascript
1// lib/importly.js
2const axios = require("axios");
3
4class ImportlyClient {
5 constructor(apiKey) {
6 this.apiKey = apiKey;
7 this.baseURL = process.env.IMPORTLY_API_URL;
8 this.client = axios.create({
9 baseURL: this.baseURL,
10 headers: {
11 Authorization: `Bearer ${apiKey}`,
12 "Content-Type": "application/json",
13 },
14 timeout: 30000,
15 });
16 }
17
18 async importMedia(url, options = {}) {
19 const {
20 includeVideo = true,
21 includeAudio = true,
22 videoQuality = "1080p",
23 audioQuality = "medium",
24 webhookUrl,
25 } = options;
26
27 try {
28 const response = await this.client.post("/import", {
29 url,
30 includeVideo,
31 includeAudio,
32 videoQuality,
33 audioQuality,
34 webhookUrl,
35 });
36
37 return response.data;
38 } catch (error) {
39 throw this.handleError(error);
40 }
41 }
42
43 async getMetadata(url, webhookUrl = null) {
44 try {
45 const params = { url };
46 if (webhookUrl) params.webhookUrl = webhookUrl;
47
48 const response = await this.client.get("/metadata", { params });
49 return response.data;
50 } catch (error) {
51 throw this.handleError(error);
52 }
53 }
54
55 async getBasicMetadata(url, webhookUrl = null) {
56 try {
57 const params = { url };
58 if (webhookUrl) params.webhookUrl = webhookUrl;
59
60 const response = await this.client.get("/metadata/basic", { params });
61 return response.data;
62 } catch (error) {
63 throw this.handleError(error);
64 }
65 }
66
67 async checkImportStatus(jobId) {
68 try {
69 const response = await this.client.get(`/import/status?id=${jobId}`);
70 return response.data;
71 } catch (error) {
72 throw this.handleError(error);
73 }
74 }
75
76 async checkMetadataStatus(jobId) {
77 try {
78 const response = await this.client.get(`/metadata/status?id=${jobId}`);
79 return response.data;
80 } catch (error) {
81 throw this.handleError(error);
82 }
83 }
84
85 async checkBasicMetadataStatus(jobId) {
86 try {
87 const response = await this.client.get(`/metadata/basic/status?id=${jobId}`);
88 return response.data;
89 } catch (error) {
90 throw this.handleError(error);
91 }
92 }
93
94 async waitForCompletion(
95 id,
96 type = "import",
97 maxWaitTime = 300000,
98 pollInterval = 5000
99 ) {
100 const startTime = Date.now();
101 const checkStatus =
102 type === "import"
103 ? this.checkImportStatus.bind(this)
104 : this.checkMetadataStatus.bind(this);
105
106 while (Date.now() - startTime < maxWaitTime) {
107 try {
108 const result = await checkStatus(id);
109 const { status } = result.data;
110
111 if (status === "completed") {
112 return result;
113 } else if (status === "failed" || status === "cancelled") {
114 throw new Error(
115 `${type} ${status}: ${result.data.error || "Unknown error"}`
116 );
117 }
118
119 // Wait before next poll
120 await new Promise((resolve) => setTimeout(resolve, pollInterval));
121 } catch (error) {
122 if (error.response?.status === 404) {
123 throw new Error(`${type} not found`);
124 }
125 throw error;
126 }
127 }
128
129 throw new Error(`${type} timed out after ${maxWaitTime}ms`);
130 }
131
132 handleError(error) {
133 if (error.response) {
134 const { status, data } = error.response;
135 const message = data?.message || data?.error || "API request failed";
136
137 switch (status) {
138 case 401:
139 return new Error("Invalid API key");
140 case 402:
141 return new Error("Insufficient credits");
142 case 429:
143 return new Error("Rate limit exceeded");
144 case 400:
145 return new Error(`Bad request: ${message}`);
146 default:
147 return new Error(`API error (${status}): ${message}`);
148 }
149 } else if (error.request) {
150 return new Error("Network error: Unable to reach Importly API");
151 } else {
152 return new Error(`Request error: ${error.message}`);
153 }
154 }
155}
156
157module.exports = ImportlyClient;

3. Express Server with Webhook Support

Create an Express server to handle imports and webhooks:

javascript
1// server.js
2const express = require("express");
3const ImportlyClient = require("./lib/importly");
4
5const app = express();
6const port = process.env.PORT || 3000;
7
8// Middleware
9app.use(express.json());
10app.use(express.urlencoded({ extended: true }));
11
12// Initialize Importly client
13const importly = new ImportlyClient(process.env.IMPORTLY_API_KEY);
14
15// In-memory storage (use database in production)
16const imports = new Map();
17const metadata = new Map();
18
19// Import endpoint
20app.post("/import", async (req, res) => {
21 try {
22 const { url, videoQuality = "1080p", audioQuality = "medium" } = req.body;
23
24 if (!url) {
25 return res.status(400).json({ error: "URL is required" });
26 }
27
28 const result = await importly.importMedia(url, {
29 videoQuality,
30 audioQuality,
31 webhookUrl: process.env.WEBHOOK_URL,
32 });
33
34 const jobId = result.data.jobId;
35
36 // Store import info
37 imports.set(jobId, {
38 id: jobId,
39 url,
40 status: "queued",
41 createdAt: new Date().toISOString(),
42 videoQuality,
43 audioQuality,
44 });
45
46 res.json({
47 success: true,
48 jobId,
49 status: "queued",
50 message: "Import started successfully",
51 });
52 } catch (error) {
53 console.error("Import error:", error);
54 res.status(500).json({ error: error.message });
55 }
56});
57
58// Metadata endpoint
59app.post("/metadata", async (req, res) => {
60 try {
61 const { url } = req.body;
62
63 if (!url) {
64 return res.status(400).json({ error: "URL is required" });
65 }
66
67 const result = await importly.getMetadata(url, process.env.WEBHOOK_URL);
68 const jobId = result.data.jobId;
69
70 // Store metadata info
71 metadata.set(jobId, {
72 id: jobId,
73 url,
74 status: "queued",
75 createdAt: new Date().toISOString(),
76 });
77
78 res.json({
79 success: true,
80 jobId,
81 status: "queued",
82 message: "Metadata request started successfully",
83 });
84 } catch (error) {
85 console.error("Metadata error:", error);
86 res.status(500).json({ error: error.message });
87 }
88});
89
90// Status endpoints
91app.get("/import/:id/status", async (req, res) => {
92 try {
93 const { id } = req.params;
94
95 // Try local storage first
96 const localImport = imports.get(id);
97 if (localImport && localImport.status === "completed") {
98 return res.json({ success: true, data: localImport });
99 }
100
101 // Check with Importly API
102 const result = await importly.checkImportStatus(id);
103
104 // Update local storage
105 if (imports.has(id)) {
106 imports.set(id, { ...imports.get(id), ...result.data });
107 }
108
109 res.json(result);
110 } catch (error) {
111 console.error("Status check error:", error);
112 res.status(500).json({ error: error.message });
113 }
114});
115
116app.get("/metadata/:id/status", async (req, res) => {
117 try {
118 const { id } = req.params;
119
120 const localMetadata = metadata.get(id);
121 if (localMetadata && localMetadata.status === "completed") {
122 return res.json({ success: true, data: localMetadata });
123 }
124
125 const result = await importly.checkMetadataStatus(id);
126
127 if (metadata.has(id)) {
128 metadata.set(id, { ...metadata.get(id), ...result.data });
129 }
130
131 res.json(result);
132 } catch (error) {
133 console.error("Metadata status check error:", error);
134 res.status(500).json({ error: error.message });
135 }
136});
137
138// Webhook endpoint
139app.post("/webhook/importly", (req, res) => {
140 try {
141 const { type, data } = req.body;
142
143 console.log("Received webhook:", { type, data });
144
145 switch (type) {
146 case "import.completed":
147 handleImportCompleted(data);
148 break;
149 case "import.failed":
150 handleImportFailed(data);
151 break;
152 case "metadata.completed":
153 handleMetadataCompleted(data);
154 break;
155 case "metadata.failed":
156 handleMetadataFailed(data);
157 break;
158 case "basic-metadata.completed":
159 handleBasicMetadataCompleted(data);
160 break;
161 case "basic-metadata.failed":
162 handleBasicMetadataFailed(data);
163 break;
164 default:
165 console.log("Unknown webhook type:", type);
166 }
167
168 res.json({ received: true });
169 } catch (error) {
170 console.error("Webhook error:", error);
171 res.status(500).json({ error: "Webhook processing failed" });
172 }
173});
174
175// Webhook handlers
176function handleImportCompleted(data) {
177 const { jobId, result } = data;
178
179 if (imports.has(jobId)) {
180 imports.set(jobId, {
181 ...imports.get(jobId),
182 status: "completed",
183 result,
184 completedAt: new Date().toISOString(),
185 });
186 }
187
188 console.log("Import completed:", jobId);
189
190 // Add your custom logic here:
191 // - Send notifications
192 // - Update database
193 // - Process the media file
194 // - Trigger downstream workflows
195}
196
197function handleImportFailed(data) {
198 const { jobId, error } = data;
199
200 if (imports.has(jobId)) {
201 imports.set(jobId, {
202 ...imports.get(jobId),
203 status: "failed",
204 error,
205 failedAt: new Date().toISOString(),
206 });
207 }
208
209 console.error("Import failed:", jobId, error);
210}
211
212function handleMetadataCompleted(data) {
213 const { jobId, result } = data;
214
215 if (metadata.has(jobId)) {
216 metadata.set(jobId, {
217 ...metadata.get(jobId),
218 status: "completed",
219 result,
220 completedAt: new Date().toISOString(),
221 });
222 }
223
224 console.log("Metadata completed:", jobId);
225}
226
227function handleMetadataFailed(data) {
228 const { jobId, error } = data;
229
230 if (metadata.has(jobId)) {
231 metadata.set(jobId, {
232 ...metadata.get(jobId),
233 status: "failed",
234 error,
235 failedAt: new Date().toISOString(),
236 });
237 }
238
239 console.error("Metadata failed:", jobId, error);
240}
241
242function handleBasicMetadataCompleted(data) {
243 const { jobId, result } = data;
244 console.log("Basic metadata completed:", jobId, result);
245 // result contains: { title, duration, thumbnail }
246}
247
248function handleBasicMetadataFailed(data) {
249 const { jobId, error } = data;
250 console.error("Basic metadata failed:", jobId, error);
251}
252
253// List endpoints for debugging
254app.get("/imports", (req, res) => {
255 const allImports = Array.from(imports.values()).sort(
256 (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
257 );
258
259 res.json({ imports: allImports });
260});
261
262app.get("/metadata", (req, res) => {
263 const allMetadata = Array.from(metadata.values()).sort(
264 (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
265 );
266
267 res.json({ metadata: allMetadata });
268});
269
270// Health check
271app.get("/health", (req, res) => {
272 res.json({
273 status: "ok",
274 timestamp: new Date().toISOString(),
275 imports: imports.size,
276 metadata: metadata.size,
277 });
278});
279
280app.listen(port, () => {
281 console.log(`Server running on port ${port}`);
282 console.log(`Webhook URL: ${process.env.WEBHOOK_URL}`);
283});

4. CLI Tool (Optional)

Create a command-line tool for easy imports:

javascript
1// cli.js
2#!/usr/bin/env node
3
4const ImportlyClient = require('./lib/importly')
5
6async function main() {
7 const args = process.argv.slice(2)
8
9 if (args.length === 0) {
10 console.log('Usage: node cli.js <url> [quality]')
11 process.exit(1)
12 }
13
14 const url = args[0]
15 const quality = args[1] || '1080p'
16
17 const importly = new ImportlyClient(process.env.IMPORTLY_API_KEY)
18
19 try {
20 console.log(`Starting import for: ${url}`)
21 console.log(`Quality: ${quality}`)
22
23 const result = await importly.importMedia(url, { videoQuality: quality })
24 const jobId = result.data.jobId
25
26 console.log(`Import started with ID: ${jobId}`)
27 console.log('Waiting for completion...')
28
29 const completed = await importly.waitForCompletion(jobId, 'import')
30
31 console.log('Import completed!')
32 console.log('Media URL:', completed.data.result.mediaUrl)
33 console.log('Credits used:', completed.data.result.creditsUsed)
34 console.log('Duration:', completed.data.result.duration + 's')
35
36 } catch (error) {
37 console.error('Error:', error.message)
38 process.exit(1)
39 }
40}
41
42if (require.main === module) {
43 main()
44}

Make it executable:

bash
1chmod +x cli.js

Use it:

bash
1node cli.js "https://example.com/video" "1080p"

5. Running the Server

Start your server:

bash
1# Development
2npm run dev
3
4# Production
5npm start

Test the endpoints:

bash
1# Start an import
2curl -X POST http://localhost:3000/import \
3 -H "Content-Type: application/json" \
4 -d '{"url": "https://example.com/video", "videoQuality": "1080p"}'
5
6# Check status
7curl http://localhost:3000/import/YOUR_IMPORT_ID/status

Why Node.js Server-Side?

Server-side processing is ideal when you need:

  • Secure API key storage - Never expose keys to clients
  • Background processing - Imports run independently of user sessions
  • Webhook reliability - Server is always available to receive notifications
  • Database integration - Easy to store and query import history
  • Batch processing - Handle multiple imports efficiently

Best Practices

  1. Use a database instead of in-memory storage for production
  2. Implement webhook signature verification for security
  3. Add request logging and monitoring
  4. Use environment variables for all configuration
  5. Implement rate limiting to protect your server
  6. Add proper error handling and retry logic
  7. Use a job queue (like Bull/Redis) for heavy workloads

Production Considerations

  • Database: Use PostgreSQL, MongoDB, or similar for persistence
  • Queue: Implement Redis/Bull for background job processing
  • Monitoring: Add logging with Winston, metrics with Prometheus
  • Security: Implement authentication, rate limiting, input validation
  • Deployment: Use PM2, Docker, or serverless platforms

Complete Example

Check out our complete Node.js example on GitHub for a full implementation with database integration and advanced features.