npm install --save multer



Multer parses incoming requests for files, so it can parse text and also files.

Storing files

views/product-add.js

<form action="/admin/product-add" enctype="multipart/form-data" method="post">
    <input type="file" name="image" id="image" />
</form>



routes/admin.js

const path = require('path');
const express = require('express');
const router = express.Router();
const productsController = require('../controllers/shop');
const isAuth = require('../middleware/is-auth');

const products = [];

router.get('/product-add', isAuth, productsController.getAddProduct);
router.post('/product-add', isAuth, productsController.postAddProduct);



controllers/shop.js

exports.postAddProduct = (req, res, next) => {
    const title = req.body.title;
    const image = req.file;
    const price = req.body.price;
    const description = req.body.description;

    if (!image) {
        return res.status(422).render('admin/edit-product', {
            hasError: true,
            errorMessage: 'Attached file is not an image'
        });
    }

    ...
        store in db
    ...
}



app.js

...
const multer = require('multer');

const fileStorage = multer.diskStorage({
    destination: (req, file, callback) => {
        callback(null, 'images');
    },
    filename: (req, file, callback) => {
        callback(null, new Date().toISOString() + '-' + file.originalname);
    }
});

const fileFilter = (req, file, callback) => {
    if (file.mimetype == 'image/png' ||
        file.mimetype == 'image/jpg' ||
        file.mimetype == 'image/jpeg'
    ) {
        callback(null, true);
    } else {
        callback(null, false);
    }    
}

app.use(multer
    ({
        storage: fileStorage,
        fileFilter: fileFilter
    })
    .single('image'));  //we are expecting single file with name image


app.use('/images', express.static(path.join(__dirname, 'images'))); //serve images as static files



Serving files

Uploaded images can be served as public static files. But for example invoices we want to serve only for users which it belongs to. We will also generate PDF file with pdfkit.



npm install --save pdfkit



routes/shop.js

...
router.get('orders/:orderId', isAuth, shopController.getInvoice);
...



controllers/shop.js

const fs = require('fs');  //file system
const path = require('path');
const Order = require('../models/order');

const PDFDocument = require('pdfkit');

exports.getInvoice = (req, res, next) => {
    const orderId = req.params.orderId; 
    Order.findById(orderId).then(order => {
        if (!order) {
            return next(new Error('No order found'));
        }
        if (order.user.userId.toString() !== req.user._id.toString()) {
            return next(new Error('Unauthorizes'));
        }

        const invoiceName = 'invoice-' + orderId + '.pdf';
        const invoicePath = path.join('data', 'invoice', invoiceName);
       

        /*
        //readFile is good only for small files, because node read content of whole file into memory. when there are lot of request, this can cause problems
        fs.readFile(invoicePath, (err, data) => {
            if (err) {
                return next(err);
            }

            res.setHeader('Content-Type', 'application/pdf');
            res.setHeader('Content-Disposition', 'attachment; filename="' + invoiceName + '"');
            res.send(data);
        });
        */


        //streaming is good for bigger files
         
        const pdfDoc = new PDFDocument();
        res.setHeader('Content-Type', 'application/pdf');
        res.setHeader('Content-Disposition', 'attachment; filename="' + invoiceName + '"');
        pfdDoc.pipe(fs.createWriteStream(invoicePath));
        pdfDoc.pipe(res);
        pdfDoc.text('Hello this is test invoice');
        pdfDoc.end();
    })
    .catch(err => next(err));
};