Image hosting API Node.js with built-in fetch.
Node 18+ has fetch and FormData built in. This snippet reads a file, posts multipart, and returns the hosted URL — no third-party dependencies.
import fs from 'node:fs';
export async function uploadImage(filePath, apiKey) {
const fd = new FormData();
fd.append('file', new Blob([fs.readFileSync(filePath)]), filePath.split('/').pop());
const headers = {};
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
const r = await fetch('https://imagetourl.cloud/api/upload', {
method: 'POST',
headers,
body: fd,
});
const body = await r.json();
if (!r.ok || body.error) throw new Error(body.error || `HTTP ${r.status}`);
return body.data.url;
}
// Example
const url = await uploadImage('./screenshot.png', process.env.IMAGETOURL_KEY);
console.log(url); // https://imagetourl.cloud/uploads/abc123.png Why use ImageToURL's API
Zero dependencies
Works with Node 18+'s global fetch and FormData. No axios, no form-data, no node-fetch. Fewer deps means fewer security patches to babysit.
Serverless-friendly
The same snippet runs in AWS Lambda, Cloudflare Workers, Vercel Edge Functions, and Deno Deploy without modification.
TypeScript-ready
Drop into a .ts file unchanged. The URL return type is string; error paths throw.
Streaming uploads
For large files, swap readFileSync for fs.createReadStream in a ReadableStream wrapper — memory-efficient for 50 MB+ uploads.
FAQ
Do I need node-fetch?
No — Node 18+ has global fetch. For older Node, npm install node-fetch@2 and require it. The API surface is identical.
What about Next.js server components?
Same code works in a Route Handler or server action. Cloudflare Pages, Vercel, and self-hosted Next.js all provide fetch globally.
Deno / Bun?
Identical. Deno has fetch and FormData built in; Bun matches Node.js's fetch API.
Can I upload a Buffer directly?
Yes: new Blob([buffer], { type: 'image/png' }) — then append to FormData. Useful when you already have image bytes in memory.
How do I upload a canvas in browser-side Node (Electron)?
canvas.toBlob(blob => uploadImage(blob)) — rename the function to accept a Blob instead of a path; the rest is identical.
Retry on 429?
Wrap the fetch in a small backoff loop. ImageToURL responds with Retry-After headers on rate limit — respect that value.
TypeScript types?
The response shape is { data: { url: string, id: string, size: number, mime: string, created_at: string } } | { error: string }. Declare as a union and narrow.
Express middleware for server-side uploads?
Accept multipart with multer, then re-POST the buffer to ImageToURL using the above function. Store the returned URL in your DB instead of local disk.