Node.js and TypeScript media SDK
dreep is the official Node.js and TypeScript SDK for Dreep. Install it with npm install dreep, then upload files from your server, presign direct uploads for browsers, build transform URLs and signed links without a network call, and run OCR or background removal. It has zero dependencies, ships ESM and CommonJS builds with its own types, and needs Node.js 18 or later.
Install and run your first upload
Create an API key on the API Keys page of the dashboard and keep it in an environment variable on your server. The signing secret is optional and only needed for signedUrl(). The cURL tab shows the request the SDK makes for you.
// npm install dreep
import fs from "node:fs";
import { Dreep } from "dreep";
const dreep = new Dreep({
apiKey: process.env.DREEP_API_KEY, // "drp_live_..."
signingSecret: process.env.DREEP_SIGNING_SECRET, // optional
});
// Resized and converted to WebP before it is stored
const asset = await dreep.upload({
file: fs.createReadStream("hero.jpg"),
folder: "marketing/2026",
transform: { width: 1200, format: "webp" },
});
asset.url; // https://cdn.dreep.cloud/api/v1/fetch/2f928a3f-...
dreep.url(asset, { width: 400 }); // no network callcurl -X POST https://api.dreep.cloud/api/v1/upload \
-H "Authorization: Bearer $DREEP_API_KEY" \
-F "file=@hero.jpg" \
-F "folder=marketing/2026" \
-F "width=1200" \
-F "format=webp"What the package gives you
A thin client over the REST API that removes the plumbing: multipart bodies, the response envelope, BIGINT strings, signature maths and error mapping.
Zero dependencies
Built on native fetch, FormData and node:crypto. Nothing else lands in your lockfile, so the SDK adds no supply-chain surface to your server.
Typed transforms
Written in TypeScript and shipped with .d.ts files. fit, format, gravity, rotate and videoCodec are typed unions, so fit: "squish" or rotate: 45 is a compile error rather than an ignored query parameter.
ESM and CommonJS
import and require both resolve. Releases publish from CI with npm provenance, linking each version to the commit that built it.
Server-only by design
The API key can read, upload and delete everything in a project, so the constructor throws if it detects a browser. Client uploads go through presigned URLs your server issues.
Server uploads
upload() accepts a Buffer, a Readable stream, a Blob or a File. Choose a destination with folder (a slug path, created if missing), key (an S3-style key whose last segment is the filename) or folderId. Omit all three and the asset lands in the project's default folder.
// A Buffer carries no name, so pass one
const avatar = await dreep.upload({
file: buffer,
filename: "me.jpg",
key: "avatars/2026/q1/me.webp",
transform: { width: 512, height: 512, fit: "cover", format: "webp", quality: 80 },
});
// Fail instead of creating folders for an unknown path
await dreep.upload({ file, folder: "invoices/2026", autoCreateFolders: false });
// Apply a saved preset on the way in
await dreep.upload({ file, presetKey: "thumbnail" });A transform passed to upload() runs before storage: the result is what gets stored, the original is not kept, and it counts against your monthly transformation quota. To keep the original and derive sizes later, upload without a transform and use url().
A Node Readable is read into memory before sending, because fetch can't stream a multipart body. For video and other large files, prefer the presigned flow below, which sends the bytes straight to Dreep storage. Each file can be up to 500 MB.
Browser uploads through your backend
Browsers never hold your API key. Your server checks who is uploading, asks Dreep for a single-use upload URL, and the browser PUTs the file there directly. The URL is valid for 10 minutes, and the Content-Type and Content-Length of the PUT must match what you presigned.
Browser asks
name, type and size
Server presigns
presignUpload()
Browser PUTs
bytes to uploadUrl
Server confirms
confirmUpload()
Asset ready
CDN URL returned
Using the App Router? The Next.js integration guide shows the same two calls as route handlers, plus serving transformed images from Dreep URLs.
Presign on the server, PUT from the browser
The server half wraps two SDK calls behind your own authenticated routes. The browser half is plain fetch with no Dreep code at all.
// Server: behind your own auth, after your own quota checks
const startUpload = async ({ userId, filename, contentType, sizeBytes }) => {
const upload = await dreep.presignUpload({
filename,
contentType,
sizeBytes,
folder: `users/${userId}`,
});
return { id: upload.id, uploadUrl: upload.uploadUrl };
};
const finishUpload = ({ id }) => dreep.confirmUpload({ id });
// Browser: no API key, only the single-use URL
const res = await fetch("/api/uploads", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ filename: file.name, contentType: file.type, sizeBytes: file.size }),
});
const { id, uploadUrl } = await res.json();
await fetch(uploadUrl, { method: "PUT", body: file, headers: { "Content-Type": file.type } });
await fetch(`/api/uploads/${id}/confirm`, { method: "POST" });Pass the file's SHA-256 as contentHash when presigning and Dreep tells you if it already stores those bytes: alreadyExists comes back true with no uploadUrl, so you skip the PUT and confirm straight away.
Transform URLs, presets and signed links
url() appends transform parameters to the URL the API returned for an asset. It is pure string work with no API key and no request, so it is safe in a render path. Each variant is generated on first request and served from cache afterwards.
dreep.url(asset, { width: 800, format: "avif", quality: 70 });
dreep.url(asset, { width: 1200, height: 630, fit: "cover", gravity: "attention" });
dreep.url(storedUrlString, { width: 400 }); // works on a saved URL too
// Save a recipe once, then reference it by key
await dreep.createPreset({
key: "thumbnail",
name: "Thumbnail",
transform: { width: 200, height: 200, fit: "cover", format: "webp" },
});
dreep.url(asset, { preset: "thumbnail" }); // ...?p=thumbnail
// Assets in a signed folder: time-limited, signed locally
dreep.signedUrl(asset, { expiresIn: 3600 });
dreep.signedUrl(asset, { expiresIn: 900, transform: { width: 800 } });signedUrl() needs signingSecret in the client config and takes the asset rather than a bare URL, because the signature is computed over its id. expiresIn is in seconds. Anyone holding the link can fetch the asset until it expires, so treat it as a bearer token and keep expiries short.
Changing a preset changes what every call site using it delivers, without a deploy. Plans include 3, 10 or 50 presets on Free, Starter and Pro.
OCR and background removal
extractText() runs OCR over an uploaded image or PDF and returns the text plus line and word blocks with bounding boxes. With a folder, the text is also stored as a .txt asset. removeBackground() stores a transparent cutout as a new asset.
const { text, blocks, savedAsset } = await dreep.extractText({
file: fs.createReadStream("receipt.jpg"),
folder: "receipts/2026", // optional: also save the text
});
// OCR on an asset already in Dreep: request the txt format
const ocr = await fetch(dreep.url(asset, { format: "txt" }));
const pageText = await ocr.text();
const cutout = await dreep.removeBackground({
file: fs.createReadStream("shoe.jpg"),
folder: "products",
});
dreep.url(cutout, { bg: "ffffff" }); // any background colour, no new removalOCR is billed per page. Every plan includes 25 background removals, and both have 1,000-unit add-on packs for $4.99.
Errors, retries and cancellation
Every failure throws a subclass of DreepError carrying the HTTP status and the API's message. Requests time out after 30 seconds; uploads have no default timeout because a large file can take minutes, so bound them with an AbortSignal. Every method accepts signal.
| Class | Status | Raised when |
|---|---|---|
| DreepAuthError | 401 | Missing, malformed or revoked API key |
| DreepValidationError | 400 | Invalid parameters; validationErrors lists each field |
| DreepNotFoundError | 404 | Unknown asset, folder, preset or upload |
| DreepConflictError | 409 | Confirming an upload whose bytes haven't landed yet |
| DreepLimitError | 402 | Plan limit reached; carries featureKey, used and limit |
| DreepConnectionError | none | Network failure, timeout or an aborted signal |
Handling a plan limit
Safe, idempotent reads are retried twice with exponential backoff. Uploads and other writes are never retried automatically, because a repeat could store a duplicate and bill a second transformation.
import { DreepError, DreepLimitError } from "dreep";
try {
const asset = await dreep.upload({
file,
folder: "avatars",
signal: AbortSignal.timeout(120_000),
});
return asset.url;
} catch (error) {
if (error instanceof DreepLimitError) {
// e.g. storage full: upgrade or buy an add-on, there is no automatic overage
console.log(error.featureKey, error.used, error.limit);
} else if (error instanceof DreepError) {
console.log(error.status, error.message);
}
}Who this is for
- Node.js or TypeScript backends, including Next.js route handlers and server actions.
- Apps where users upload from the browser through presigned URLs your server issues.
- Teams that want typed transform parameters and no third-party packages at runtime.
- Workers and scripts that bulk upload, list or clean up media with listAllMedia().
When Dreep is not the right fit
- You want to call Dreep from browser or React Native code with the API key. The SDK refuses to run in a browser.
- You run Node.js older than 18. The SDK relies on native fetch.
- You need a UI upload widget. The package is an API client; build the picker yourself.
Frequently asked questions
Does the dreep package work with both import and require?
Yes. It ships an ESM build and a CommonJS build, with type declarations for both, so import { Dreep } from "dreep" and require("dreep") resolve on Node.js 18 or later.
How do I upload from the browser without exposing my API key?
Call presignUpload() on your server with the file's name, type and size, return the id and uploadUrl to the browser, let it PUT the file to uploadUrl, then call confirmUpload({ id }) on your server. The upload URL is single-use and valid for 10 minutes.
Does url() make a request to Dreep?
No. It appends query parameters such as width, format and quality to the asset's CDN URL. The transformed variant is generated the first time that URL is requested and served from cache after that.
What is the difference between transform on upload and url()?
A transform passed to upload() runs once before storage and replaces the original. url() leaves the original intact and derives any number of sizes and formats from it on request. Both count against the monthly transformation quota when a new variant is generated.
Which errors should I handle?
Catch DreepError for everything. Branch on DreepLimitError (402) to tell users a plan limit was reached, DreepConflictError (409) to retry a confirm, and DreepConnectionError for network failures, timeouts and aborted signals.
Is the SDK open source?
Yes. It is MIT licensed and developed on GitHub as IndigoSoftwares21/dreep-js, and each npm release is published from CI with provenance.
Related
Next.js integration
Route handlers for presigned uploads and Dreep image URLs in the App Router.
All SDKs
JavaScript and Go side by side, plus REST for every other language.
Go SDK
The same API from Go, with streaming uploads and context-first methods.
Secure file uploads
What Dreep validates on upload and what your backend still owns.
SDK documentation
Every method, parameter and return shape in the Node SDK reference.
Source on GitHub
Read the code, open an issue or send a pull request.
Pricing
Plans from $0, with storage, transformation and preset limits.
Install dreep and upload your first file
Create a free project for 1 GB of storage and 100 image transformations a month, copy an API key, and run npm install dreep.