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
139// Webhooks are sent only on successful completion. Route on data.type.
140app.post("/webhook/importly", (req, res) => {
141 try {
142 const { data } = req.body;
143
144 console.log("Received webhook:", data);
145
146 switch (data.type) {
147 case "import":
148 handleImportCompleted(data);
149 break;
150 case "metadata":
151 handleMetadataCompleted(data);
152 break;
153 case "basic-metadata":
154 handleBasicMetadataCompleted(data);
155 break;
156 default:
157 console.log("Unknown webhook type:", data.type);
158 }
159
160 res.json({ received: true });
161 } catch (error) {
162 console.error("Webhook error:", error);
163 res.status(500).json({ error: "Webhook processing failed" });
164 }
165});
166
167// Webhook handlers. Result fields are flattened directly into `data`.
168function handleImportCompleted(data) {
169 const { jobId } = data;
170
171 if (imports.has(jobId)) {
172 imports.set(jobId, {
173 ...imports.get(jobId),
174 status: "completed",
175 result: data,
176 completedAt: new Date().toISOString(),
177 });
178 }
179
180 console.log("Import completed:", jobId);
181
182 // Add your custom logic here:
183 // - Send notifications
184 // - Update database
185 // - Process the media file
186 // - Trigger downstream workflows
187}
188
189function handleMetadataCompleted(data) {
190 const { jobId } = data;
191
192 if (metadata.has(jobId)) {
193 metadata.set(jobId, {
194 ...metadata.get(jobId),
195 status: "completed",
196 result: data,
197 completedAt: new Date().toISOString(),
198 });
199 }
200
201 console.log("Metadata completed:", jobId);
202}
203
204function handleBasicMetadataCompleted(data) {
205 const { jobId } = data;
206 console.log("Basic metadata completed:", jobId, data);
207 // data contains flattened: title, duration, thumbnail
208}
209
210// List endpoints for debugging
211app.get("/imports", (req, res) => {
212 const allImports = Array.from(imports.values()).sort(
213 (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
214 );
215
216 res.json({ imports: allImports });
217});
218
219app.get("/metadata", (req, res) => {
220 const allMetadata = Array.from(metadata.values()).sort(
221 (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
222 );
223
224 res.json({ metadata: allMetadata });
225});
226
227// Health check
228app.get("/health", (req, res) => {
229 res.json({
230 status: "ok",
231 timestamp: new Date().toISOString(),
232 imports: imports.size,
233 metadata: metadata.size,
234 });
235});
236
237app.listen(port, () => {
238 console.log(`Server running on port ${port}`);
239 console.log(`Webhook URL: ${process.env.WEBHOOK_URL}`);
240});

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('Cost (USD):', completed.data.result.costInDollars)
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.