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.
The route
6 steps to Done
- 01
Install dependencies & configure storage
Prepare the project with required libraries and a folder for saved images.
Preview prompt + verify gate ▾ Hide ▴
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?
- 02
Create backend upload endpoint
Expose an API route that accepts an image file via multipart/form-data.
Preview prompt + verify gate ▾ Hide ▴
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`?
- 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 ▾ Hide ▴
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?
- 04
Persist image metadata to the database
Store original and resized filenames, dimensions, and upload timestamp for later retrieval.
Preview prompt + verify gate ▾ Hide ▴
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?
- 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 ▾ Hide ▴
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?
- 06
End‑to‑end verification & cleanup
Confirm that the full upload‑resize‑persist cycle works and remove any temporary files.
Preview prompt + verify gate ▾ Hide ▴
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?