Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Building Basic Cloudflare Transform images Api Clone with TypeScript

Cloudflare's Images service provides a powerful Transform API that allows you to optimize and manipulate images. In this tutorial, we will try to implement our own basic version using TypeScript. What We're…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Cloudflare's Images service provides a powerful Transform API that allows you to optimize and manipulate images. In this tutorial, we will try to implement our own basic version using TypeScript.






What We're Building



transformation API that lets you:




  • Resize images by width and height

  • Convert between formats (PNG, JPG, WebP)

  • Adjust image quality



Our API will follow a URL-based transformation pattern similar to Cloudflare's Image API: /transform/[options]/[image-path]






Prerequisites




  • Node.js and npm installed

  • Basic knowledge of TypeScript and Express

  • An Upstash account for Redis (used for rate limiting)






Project Setup



Let's start by creating our project structure and installing dependencies:




mkdir picform
cd picform
npm init -y






Now install the required dependencies:




npm install express sharp zod @upstash/ratelimit
npm install -D tsx tsconfig-paths tsup @types/express typescript






Let's look at what each package does:





  • express: Our web server framework


  • sharp: High-performance image processing library


  • zod: TypeScript-first schema validation


  • @upstash/ratelimit: Rate limiting with Upstash Redis


  • tsx: TypeScript execution environment


  • tsconfig-paths: For path aliases in TypeScript


  • tsup: TypeScript bundler for building our application






Project Structure



Our project follows a feature-based structure:




/
├── uploads/ # Directory for uploaded images
├── src/
│ ├── features/ # Feature modules
│ │ └── transform/ # Image transformation feature
│ ├── lib/ # Shared utilities
│ └── server.ts # Main application entry
├── .env # Environment variables
└── tsconfig.json # TypeScript configuration









Configuration Files



Let's set up our TypeScript configuration first:






tsconfig.json






{
"compilerOptions": {
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"module": "ESNext",
"noEmit": true,
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"outDir": "dist",
"sourceMap": true,
"lib": ["es2022"],
"paths": {
"@lib/*": ["./src/lib/*"],
"@features/*": ["./src/features/*"]
}
}
}









.env






UPSTASH_REDIS_REST_URL=""
UPSTASH_REDIS_REST_TOKEN=""






Copy this file to .env and fill in your Upstash credentials.






package.json Scripts



Update your package.json with the following scripts:




{
"scripts": {
"dev": "tsx --watch --env-file=.env src/server.ts",
"build": "tsup src/",
"start": "node --env-file=.env dist/server.js"
}
}








  • dev: Runs the development server with hot reloading


  • build: Bundles the TypeScript code into JavaScript using tsup


  • start: Runs the production build






Building the Core Libraries



Let's create some utility functions first:






src/lib/constants.ts






export const UPLOAD_PATH = "/uploads";






This constant defines where our images will be stored.






src/lib/errors.ts






export class AppError extends Error {
constructor(message: string) {
super(message);
this.name = "AppError";
}
}






This custom error class helps us distinguish application-specific errors from system errors.






src/lib/safe.ts



This utility provides a convenient way to handle errors in our application:




export type Safe<T> =
| {
success: true;
data: T;
}
| {
success: false;
error: unknown;
};

export function safe<T>(promise: Promise<T>, err?: unknown): Promise<Safe<T>>;
export function safe<T>(func: () => T, err?: unknown): Safe<T>;
export function safe<T>(
promiseOrFunc: Promise<T> | (() => T),
err?: unknown,
): Promise<Safe<T>> | Safe<T> {
if (promiseOrFunc instanceof Promise) {
return safeAsync(promiseOrFunc, err);
}
return safeSync(promiseOrFunc, err);
}

export async function safeAsync<T>(
promise: Promise<T>,
err?: unknown,
): Promise<Safe<T>> {
try {
const data = await promise;
return { data, success: true };
} catch (e) {
if (err !== undefined) {
return { success: false, error: err };
}
return { success: false, error: e };
}
}

export function safeSync<T>(func: () => T, err?: unknown): Safe<T> {
try {
const data = func();
return { data, success: true };
} catch (e) {
console.error(e);
if (err !== undefined) {
return { success: false, error: err };
}
if (e instanceof Error) {
return { success: false, error: e.message };
}
return { success: false, error: e };
}
}









src/lib/upstash.ts






import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

export const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(2, "3 m"),
analytics: true,
});






This configures rate limiting to allow 2 requests per IP per image path in a 3-minute sliding window.






Type Definitions



Create a type definition file to extend Express's Request interface:






type.d.ts






import { Transform } from "@features/transform/schema";

declare global {
namespace Express {
interface Request {
transformOptions?: Transform["options"];
transformPath?: Transform["path"];
}
}
}






This adds our custom properties to the Express Request type.






Building the Transform Feature



Now let's create the core transformation feature:






src/features/transform/schema.ts






import { z } from "zod";

const format = z.enum(["png", "jpg", "webp"]);

export const transformSchema = z.object({
path: z.custom<`${string}.${z.infer<typeof format>}`>(
(val) => {
if (typeof val !== "string") return false;
return /\.(png|jpg|webp)$/i.test(val);
},
{
message: "Path must end with .png, .jpg, or .webp",
}
),
options: z.object({
width: z.coerce.number().min(100).max(1920).optional(),
height: z.coerce.number().min(100).max(1080).optional(),
quality: z.coerce.number().min(1).max(100).optional(),
format: format.optional(),
}),
});

export type Transform = z.infer<typeof transformSchema>;






This Zod schema defines our API's validation rules:




  • Images must have valid extensions (.png, .jpg, or .webp)

  • Width can be between 100-1920px

  • Height can be between 100-1080px

  • Quality can be between 1-100






src/features/transform/service.ts



This is where the image transformation happens using Sharp:




import { AppError } from "@lib/errors";
import { type Transform } from "./schema";
import fs from "fs";
import path from "path";
import sharp from "sharp";
import { UPLOAD_PATH } from "@lib/constants";

export const transformService = async (data: Transform) => {
// Construct the full file path
const _path = path.join(process.cwd(), UPLOAD_PATH, data.path);

// Check if the image exists
const imageExists = fs.existsSync(_path);
if (!imageExists) {
throw new AppError("Image does not exist");
}

// Extract file extension from path and use as default format
const fileExt = path
.extname(_path)
.split(".")[1] as Transform["options"]["format"];
const format = data.options.format ?? fileExt;

// Read the image file
const image = fs.readFileSync(_path);

// Initialize Sharp and resize the image
const resize = sharp(image).resize({
height: data.options.height,
width: data.options.width,
});

// Apply format-specific transformations
switch (format) {
case "png":
resize.png({ quality: data.options.quality });
break;
case "jpg":
resize.jpeg({ quality: data.options.quality });
break;
case "webp":
resize.webp({ quality: data.options.quality });
break;

default:
throw new AppError("Invalid format");
}

// Generate the transformed image buffer with metadata
const result = await resize.toBuffer({ resolveWithObject: true });
return result;
};









src/features/transform/middleware.ts



Here we handle request parsing and rate limiting:




import { type NextFunction, type Request, type Response } from "express";
import { transformSchema } from "./schema";
import { ratelimit } from "@lib/upstach";

export const parseTransformRequest = (
req: Request,
res: Response,
next: NextFunction
) => {
// Extract options and path from URL parameters
const _options = req.params[0];
const path = req.params[1];

if (!_options || !path) {
res.status(400).send("params missing");
return;
}

// Parse comma-separated options into key-value object
let options = _options
.split(",")
.reduce<Record<string, unknown>>((acc, current) => {
const [key, value] = current.split("=");
acc[key!] = value;
return acc;
}, {});

// Validate input using Zod schema
const validated = transformSchema.safeParse({
path,
options,
});

if (!validated.success) {
res.status(400).send({
success: false,
message: "invalid request",
errors: validated.error.errors.map((i) => ({
path: Array.isArray(i.path) ? i.path[1] : i.path,
message: i.message,
})),
});
return;
}

// Store validated data in request object for next middleware
req.transformOptions = validated.data.options;
req.transformPath = validated.data.path;

next();
};

export const rateLimit = async (
req: Request,
res: Response,
next: NextFunction
) => {
const path = req.transformPath;

if (!path) {
res.status(400).send("middleware missing");
return;
}

// Create unique key for rate limiting (IP + image path)
const key = `${req.ip}-${path}`;

// Apply rate limit
const limit = await ratelimit.limit(key);
if (!limit.success) {
// Calculate retry time and send appropriate headers
const retry = Math.max(0, Math.floor((limit.reset - Date.now()) / 1000));
res.setHeader("Retry-After", retry);
res.status(429).send({
success: false,
message: "too many requests",
});
return;
}

return next();
};









src/features/transform/controller.ts






import { type Response, type Request } from "express";
import { transformService } from "./service";
import { safe } from "@lib/safe";
import { AppError } from "@lib/errors";

export const transformController = async (req: Request, res: Response) => {
const path = req.transformPath;
const options = req.transformOptions;

if (!options || !path) {
res.status(400).send("middleware missing");
return;
}

// Process the image transformation with error handling
const data = await safe(transformService({ path, options }));
if (!data.success) {
// Handle application-level errors with 400 status
if (data.error instanceof AppError) {
res.status(400).send({
success: false,
message: data.error.message,
});
return;
}
// Handle system errors with 500 status
res.status(500).send({
success: false,
message: "Something went wrong",
});
return;
}

// Set appropriate headers for the image response
res.setHeader(
"Content-Disposition",
`inline; filename=transformed.${data.data.info.format}`
);
res.setHeader("Content-Type", `image/${data.data.info.format}`);
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");

// Send the transformed image
res.send(data.data.data);
};






here we are Setting the cache headres so hopefuly the CDN takes care of caching and serving the images that were allready transformed






src/features/transform/route.ts






import { Router } from "express";
import { transformController } from "./controller";
import { parseTransformRequest, rateLimit } from "./middleware";

const transformRouter = Router();

// Define the route using regex pattern to capture parameters
transformRouter.get(
/^\/transform\/([a-zA-Z0-9=,]+)\/(.+)$/,
parseTransformRequest,
rateLimit,
transformController
);

export default transformRouter;









Main Server File



Finally, let's set up our main server file:






src/server.ts






import transformRouter from "@features/transform/route";
import { UPLOAD_PATH } from "@lib/constants";
import express from "express";
import fs from "fs";
import path from "path";

const app = express();

// Create uploads directory if it doesn't exist
const uploadPath = path.join(process.cwd(), UPLOAD_PATH);
if (!fs.existsSync(uploadPath)) {
fs.mkdirSync(uploadPath);
}

// Register our transform router
app.use(transformRouter);

// Start the server
app.listen(3000, () => {
console.log("Server is running on port 3000");
});









Using the API



With everything set up, you can now use the API:




http://localhost:3000/transform/width=500,height=300,quality=80,format=webp/example-image.png






The format is:




/transform/[options]/[image-path]






Where:





  • options are comma-separated key=value pairs


  • image-path is the relative path to the image in the uploads directory






Building for Production



When you're ready to deploy:




  1. Run npm run build to compile the TypeScript code

  2. Use npm start to run the production build






Conclusion



In this tutorial, we've built a basic image transformation API inspired by Cloudflare's Image API. The implementation uses TypeScript, Express, Sharp, and Upstash to provide a clean, well-structured service with:




  • Type-safe request handling

  • Input validation with Zod

  • Rate limiting with Upstash

  • High-performance image processing with Sharp

  • Clear separation of concerns through a feature-based architecture



Github Repo Link (⭐ if u like it)

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - Building Basic Cloudflare Transform images Api Clone with TypeScript
id: a4382a3d-46bb-4584-8a4d-f31c84a0bd0e
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-26
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-26"
        description = "YARA Signature for "
    strings:
        $str = "Building Basic Cloudflare Tran" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("Building Basic Cloudflare Transform imag")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*Building Basic Cloudflare Transform imag*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "Building Basic Cloudflare Transform imag"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Building Basic Cloudflare Transform imag.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Building Basic Cloudflare Transform images Api Clone with TypeScript

Thematisch verwandte Begriffe: Building, Basic, Cloudflare, Transform · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-86066 | Horilla is an HR and CRM software. Prior to 2.0.0, approve_validate_atte…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag