Upload files to disk or S3
Build an avatar endpoint: accept an image, validate its size and type, store it, and serve it back.
1. Accept an upload
Point uploads at a folder, and file fields on a multipart form land on ctx.body:
import server from '@server/next';
export default server({ uploads: './uploads' })
.post('/avatar', (ctx) => {
console.log(ctx.body.avatar);
// { name, path, type, size }
return 201;
});Files stream to storage as they arrive, so they are never buffered whole in memory.
2. Validate size and type
Swap the folder for the object form to reject files before your handler runs:
export default server({
uploads: {
bucket: './uploads',
maxFileSize: '5mb',
fileType: ['image/jpeg', 'image/png'],
},
})
.post('/avatar', (ctx) => ({ path: ctx.body.avatar.path }));An oversized or wrong-type file is rejected automatically, the handler never runs.
3. Serve it back
Build the bucket yourself and pass it as uploads, so your routes can read from it too. Return a stored file and it streams back with the right Content-Type. Guard it however you like first, an auth check, an ownership check:
import server, { bucket } from '@server/next';
const uploads = bucket.FS('./uploads');
export default server({ uploads })
.get('/avatar/:id', (ctx) => {
// if (!ctx.user) return 401;
return uploads.file(ctx.url.params.id);
});A missing file returns 404 for you. See Serving stored files for signed URLs and cloud buckets.
Next steps
- Store files in an S3-compatible bucket for production.
- Accept several files by repeating the field name,
ctx.body.photosbecomes an array. - Give each kind of file its own destination and limits with per-route
uploads, like.post('/video', { uploads: videos }, ...).