Peter Weightman
Back

Using vitejs with express

This weekend I started a new side project and wanted to try out Vite as I've seen good things about it on twitter, however I wanted to serve the app from an express server, that will also handle API endpoints.

It probably would've been easier to just use the normal vite commands, then have the express server running on a different port, and configure a proxy so that API requests go to the express server port, but I wanted to be able to just run a single command rather than having the run vite and the server separately.

To start off, I scaffolded a vue 3 project with npm init @vitejs/app my-vue-app -- --template vue.

To get it served from an express server, you can leave all the src code alone, and create a server.js file, as below which is heavily based on the example given in the SSR section of the docs.

You'll just need to install express, npm install --save express.

const express = require('express');
const fs = require('fs');
const path = require('path');
const PORT = 4000;

const isTest = process.env.NODE_ENV === 'test';

async function createServer(
	root = process.cwd(),
	isProd = process.env.NODE_ENV === 'production'
) {
	const resolve = (p) => path.resolve(__dirname, p);

	const app = express();

	/**
	 * @type {import('vite').ViteDevServer}
	 */
	let vite;
	if (isProd) {
		// TODO: app.use(require('compression')());
		app.use(express.static(resolve('./dist')));
	} else {
		vite = await require('vite').createServer({
			root,
			logLevel: isTest ? 'error' : 'info',
			server: {
				middlewareMode: true,
				watch: {
					// During tests we edit the files too fast and sometimes chokidar
					// misses change events, so enforce polling for consistency
					usePolling: true,
					interval: 100,
				},
			},
		});
		// use vite's connect instance as middleware
		app.use(vite.middlewares);
	}

	app.use('*', async (req, res) => {
		const url = req.originalUrl;

		const template = fs.readFileSync(
			resolve(isProd ? './dist/index.js' : './index.html'),
			'utf-8'
		);

		res.status(200).set({ 'Content-Type': 'text/html' }).end(template);
	});

	return { app, vite };
}

if (!isTest) {
	createServer().then(({ app }) =>
		app.listen(PORT, () => {
			console.log(`Listening on http://localhost:${PORT}`);
		})
	);
}

// for test use
exports.createServer = createServer;

Then you'll need to update the package.json scripts and npm insall --save-dev cross-env.

"scripts": {
	"dev": "cross-env NODE_ENV=development node ./server.js",
	"serve": "cross-env NODE_ENV=production node ./server.js",
	"build": "vite build"
},

Seems to be working well so far, although perhaps if I were to start another project, I'd just keep the dev servers separate and use a proxy to get api requests to go to the api server port.