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 checkImportStatus(jobId) {
56 try {
57 const response = await this.client.get(`/import/status?id=${jobId}`);
58 return response.data;
59 } catch (error) {
60 throw this.handleError(error);
61 }
62 }
63
64 async checkMetadataStatus(jobId) {
65 try {
66 const response = await this.client.get(`/metadata/status?id=${jobId}`);
67 return response.data;
68 } catch (error) {
69 throw this.handleError(error);
70 }
71 }
72
73 async waitForCompletion(
74 id,
75 type = "import",
76 maxWaitTime = 300000,
77 pollInterval = 5000
78 ) {
79 const startTime = Date.now();
80 const checkStatus =
81 type === "import"
82 ? this.checkImportStatus.bind(this)
83 : this.checkMetadataStatus.bind(this);
84
85 while (Date.now() - startTime < maxWaitTime) {
86 try {
87 const result = await checkStatus(id);
88 const { status } = result.data;
89
90 if (status === "completed") {
91 return result;
92 } else if (status === "failed" || status === "cancelled") {
93 throw new Error(
94 `${type} ${status}: ${result.data.error || "Unknown error"}`
95 );
96 }
97
98 // Wait before next poll
99 await new Promise((resolve) => setTimeout(resolve, pollInterval));
100 } catch (error) {
101 if (error.response?.status === 404) {
102 throw new Error(`${type} not found`);
103 }
104 throw error;
105 }
106 }
107
108 throw new Error(`${type} timed out after ${maxWaitTime}ms`);
109 }
110
111 handleError(error) {
112 if (error.response) {
113 const { status, data } = error.response;
114 const message = data?.message || data?.error || "API request failed";
115
116 switch (status) {
117 case 401:
118 return new Error("Invalid API key");
119 case 402:
120 return new Error("Insufficient credits");
121 case 429:
122 return new Error("Rate limit exceeded");
123 case 400:
124 return new Error(`Bad request: ${message}`);
125 default:
126 return new Error(`API error (${status}): ${message}`);
127 }
128 } else if (error.request) {
129 return new Error("Network error: Unable to reach Importly API");
130 } else {
131 return new Error(`Request error: ${error.message}`);
132 }
133 }
134}
135
136module.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 default:
159 console.log("Unknown webhook type:", type);
160 }
161
162 res.json({ received: true });
163 } catch (error) {
164 console.error("Webhook error:", error);
165 res.status(500).json({ error: "Webhook processing failed" });
166 }
167});
168
169// Webhook handlers
170function handleImportCompleted(data) {
171 const { jobId, result } = data;
172
173 if (imports.has(jobId)) {
174 imports.set(jobId, {
175 ...imports.get(jobId),
176 status: "completed",
177 result,
178 completedAt: new Date().toISOString(),
179 });
180 }
181
182 console.log("Import completed:", jobId);
183
184 // Add your custom logic here:
185 // - Send notifications
186 // - Update database
187 // - Process the media file
188 // - Trigger downstream workflows
189}
190
191function handleImportFailed(data) {
192 const { jobId, error } = data;
193
194 if (imports.has(jobId)) {
195 imports.set(jobId, {
196 ...imports.get(jobId),
197 status: "failed",
198 error,
199 failedAt: new Date().toISOString(),
200 });
201 }
202
203 console.error("Import failed:", jobId, error);
204}
205
206function handleMetadataCompleted(data) {
207 const { jobId, result } = data;
208
209 if (metadata.has(jobId)) {
210 metadata.set(jobId, {
211 ...metadata.get(jobId),
212 status: "completed",
213 result,
214 completedAt: new Date().toISOString(),
215 });
216 }
217
218 console.log("Metadata completed:", jobId);
219}
220
221function handleMetadataFailed(data) {
222 const { jobId, error } = data;
223
224 if (metadata.has(jobId)) {
225 metadata.set(jobId, {
226 ...metadata.get(jobId),
227 status: "failed",
228 error,
229 failedAt: new Date().toISOString(),
230 });
231 }
232
233 console.error("Metadata failed:", jobId, error);
234}
235
236// List endpoints for debugging
237app.get("/imports", (req, res) => {
238 const allImports = Array.from(imports.values()).sort(
239 (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
240 );
241
242 res.json({ imports: allImports });
243});
244
245app.get("/metadata", (req, res) => {
246 const allMetadata = Array.from(metadata.values()).sort(
247 (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
248 );
249
250 res.json({ metadata: allMetadata });
251});
252
253// Health check
254app.get("/health", (req, res) => {
255 res.json({
256 status: "ok",
257 timestamp: new Date().toISOString(),
258 imports: imports.size,
259 metadata: metadata.size,
260 });
261});
262
263app.listen(port, () => {
264 console.log(`Server running on port ${port}`);
265 console.log(`Webhook URL: ${process.env.WEBHOOK_URL}`);
266});

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.