Skip to content
Flows
App Builder Generated Intermediate · 60-90 minutes

Add Image Upload & Resizing Feature

Integrate a backend image upload endpoint that resizes images and stores them, plus a front‑end component to use it.

Start Route · 6 steps

The route

6 steps to Done

  1. 01

    Install dependencies & configure storage

    Prepare the project with required libraries and a folder for saved images.

    Preview prompt + verify gate ▾

    1. Run npm install sharp multer. 2. In the project root, create a folder named public/uploads if it does not exist. 3. Add a .gitignore entry to exclude uploaded files. 4. Ensure the import statements are added to the main server file (e.g., const multer = require('multer'); const sharp = require('sharp');). 5. Export a configured multer storage that writes files to ./public/uploads preserving original filenames. 6. Verify the server starts without module‑not‑found errors.

    • Can you run `npm install` without errors?
    • Does the folder `public/uploads` exist on disk?
    • Does the server start (`node server.js`) with no import errors?
  2. 02

    Create backend upload endpoint

    Expose an API route that accepts an image file via multipart/form-data.

    Preview prompt + verify gate ▾

    1. In routes/images.js (create if missing), import Express and the `upload` middleware from step 1. 2. Define router.post('/upload', upload.single('image'), (req, res) => { if (!req.file) return res.status(400).json({error:'No file'}); const filename = req.file.filename; res.json({filename}); }); 3. Export the router and mount it in server.js with app.use('/api/images', imagesRouter). 4. Ensure the route is reachable with a POST request and returns a 200 JSON payload containing `filename`.

    • Does `curl -F image=@path/to/pic.jpg http://localhost:3000/api/images/upload` return a 200 status?
    • Is the response JSON containing a `filename` key?
    • Is the uploaded file present in `public/uploads`?
  3. 03

    Resize image with Sharp

    Generate multiple sized versions (e.g., thumbnail 150x150, medium 600x600) and store them alongside the original.

    Preview prompt + verify gate ▾

    1. In the upload route handler, after responding with the original filename, call an async function `processImage(filePath)`. 2. Implement `processImage` to: a) Read the file at `filePath`. b) Use sharp to create a 150x150 version saved as `${basename}_thumb${ext}` in the same folder. c) Create a 600x600 version saved as `${basename}_medium${ext}`. d) Return paths of the resized files. 3. Ensure errors are caught and logged; the original response should still include the original filename plus paths of resized images. 4. Verify the two new files exist on disk.

    • After upload, are there three files with expected naming patterns?
    • Do the thumbnail and medium files have the correct dimensions (check with an image viewer or `identify`)?
    • Is there no unhandled exception in the server console?
  4. 04

    Persist image metadata to the database

    Store original and resized filenames, dimensions, and upload timestamp for later retrieval.

    Preview prompt + verify gate ▾

    1. Verify `prisma` client is available (require('@prisma/client')). 2. In the upload route, after resizing, call `await prisma.image.create({ data: { originalPath: filename, thumbPath: thumbFilename, mediumPath: mediumFilename, uploadedAt: new Date() } });`. 3. Return the created record’s id in the JSON response. 4. If Prisma isn’t set up, create a tiny JSON file store `data/images.json` and push a new entry with the same fields. 5. Ensure the DB entry is committed and can be read back via a GET /api/images/:id route (optional).

    • Does the JSON response contain an `id` field?
    • Can you locate a row in the Image table with the uploaded filenames?
    • Is the `uploadedAt` timestamp present and recent?
  5. 05

    Build front‑end image upload component

    Provide a UI for users to select an image, preview it, and submit to the new endpoint.

    Preview prompt + verify gate ▾

    1. In src/components/ImageUploader.jsx create a functional component. 2. Use useState for `file`, `previewUrl`, and `response`. 3. On file selection, store the File object and set previewUrl = URL.createObjectURL(file). 4. Render an <img> with previewUrl, a <input type='file'> (accept='image/*'), and a button "Upload". 5. On button click, build a FormData, append('image', file), and POST to `/api/images/upload`. 6. Await JSON response, store it in `response` state, and display the returned filenames and ids. 7. Handle errors with try/catch and show a message. 8. Import and render this component in App.jsx. 9. Ensure the app builds (`npm run dev`) and the component works in the browser.

    • Does selecting a file display a preview image?
    • After clicking Upload, does the response JSON appear on screen?
    • Are there any red errors in the browser console?
  6. 06

    End‑to‑end verification & cleanup

    Confirm that the full upload‑resize‑persist cycle works and remove any temporary files.

    Preview prompt + verify gate ▾

    1. Using the UI from step 5, upload a small JPEG. 2. After success, open the terminal and list `public/uploads` – you should see three files (original, *_thumb*, *_medium*). 3. Open your database client (e.g., Prisma Studio) and locate the new Image row; verify filenames match the files on disk. 4. Write a short Node script `test/uploadTest.js` that uses `node-fetch` to POST a sample image, then checks `fs.existsSync` for the three files and queries Prisma for the row. 5. Run the script; it should log “PASS”. 6. After verification, delete the three files and the database row (use Prisma delete). 7. Commit the new files (ImageUploader.jsx, routes, test script) to version control.

    • Did the UI upload return an ID and filenames?
    • Do the three image files exist on disk after upload?
    • Does the automated script output "PASS" and clean up after itself?