DEV Community

Jamund Ferguson
Jamund Ferguson

Posted on • Updated on

Adding a REST API to your Vite Server in 5 Seconds

Vite and Express

Like many others I've been blown way by the incredible performance and capabilities of vite as a tool for rapidly building out React apps. In my role as an egghead educator and a Developer Advocate at PayPal I'm looking for tools that install quickly, have minimal setup costs and let me start building immediately. vite does all that and more, but in the past I often run into one limitation: I need some kind of API to complement my apps.

vite-plugin-mix on github

I've been using express to build out APIs pretty much since it first came out. Checkout my course on using express 5 if you want a quick tutorial. Express makes it easy to build out APIs and it's super easy to add express support to your vite app. You just need a single plugin: vite-plugin-mix.


I promised 5 seconds, so get ready to copy & paste!

npm install -D vite-plugin-mix
Enter fullscreen mode Exit fullscreen mode

Then in your vite.config.js file add this to your plugins array

import { defineConfig } from 'vite'
import mix from 'vite-plugin-mix'

export default defineConfig({
  plugins: [
    mix({
      handler: './api.js',
    }),
  ],
})
Enter fullscreen mode Exit fullscreen mode

And then in api.js type:

import express from 'express';

const app = express();

app.get("/api/hello", (req, res) => {
  res.json({ hello: "world" });
});

export const handler = app;
Enter fullscreen mode Exit fullscreen mode

Once you do that you can startup vite with npm run dev and like magic you'll have the ability to reference /api/hello on your local dev server. Like everything else in vite, if you make any changes to your API they'll be available immediately without having to restart anything. Just edit the code and call the route again and you'll see the latest!

One little note: I have only used vite so far for local development and can't personally vouch for it for production apps. If you're looking for something a bit more production ready you might want to check out fastify-vite which combines another popular API server, fastify with vite for one powerful and fast full-stack development tool.

Happy hacking friends!

Top comments (6)

Collapse
 
techieoriname profile image
Techie Oriname

You missed out the import line at the top of vite.config.js

import { defineConfig } from 'vite'
import mix from 'vite-plugin-mix'

export default defineConfig({
  plugins: [
    mix({
      handler: './api.js',
    }),
  ],
})
Enter fullscreen mode Exit fullscreen mode
Collapse
 
cefn profile image
Cefn Hoile • Edited

I found that the issue github.com/egoist/vite-plugin-mix/... makes this strategy impossible with current vite and latest typescript since vite-plugin-mix is not currently bundled in a form suitable for a modern ESM project.

You get an error like "TypeError: mix is not a function".

Instead, I installed npm-run-all and cors, and added script targets to the package.json of my project like this. The --race option means killing either the frontend server or the backend server will close them both.

    "dev": "run-p --race frontend backend",
    "frontend": "vite",
    "backend": "NODE_OPTIONS='--loader ts-node/esm' ts-node ./src/server.ts",
Enter fullscreen mode Exit fullscreen mode

The backend target should not just export a pluggable middleware , but launch an actual server, (with a listen) with a cors middleware allowing hits against the distinct port, so ./src/server.ts should look like this...

import express from "express";
import cors from "cors";

const port = 8000;

const corsOptions = {
  origin: "http://localhost:5173",
  optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
};

const app = express();
app.use(cors(corsOptions));

app.get("/api/hello", (req, res) => {
  res.json({ hello: "world" });
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
asdf23 profile image
asdf23

I love this solution, but I can't get it to work any more. I suppose vite changed. Now to run vite from the command like I need to run npx vite.. tried changing the frontend to that but it didn't work. Any suggestions?

Collapse
 
asdf23 profile image
asdf23

Found it:

    "dev": "run-p frontend backend",
    "frontend": "npx vite",
    "backend": "node ./src/server.js"
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kevinmlong profile image
Kevin Long

Great article! Is it possible to use TypeScript for the "backend" stuff? My use case would be continuing with client side rendering of a Vue app and having some routes be "backend" endpoints handled with express.

Collapse
 
jw1_dev profile image
Jacky Wong

looks like the mix plugin is causing a problem, i removed the plugin and then everything worked perfectly, still don't know what's triggering it.

this is what vite logged:

8:23:28 PM [vite] server restarted.

  > Local: http://localhost:3000/  
  > Network: use `--host` to expose
8:25:25 PM [vite] vite.config.js changed, restarting server...
8:25:25 PM [vite] server restarted.

  > Local: http://localhost:3000/  
  > Network: use `--host` to expose
@vant/touch-emulator doesn't appear to be written in CJS, but also doesn't appear to be a valid ES module (i.e. it doesn't have "type": "module" or an .mjs extension for the entry point). Please contact the package author to fix.
Failed to parse source for import analysis because the content contains invalid JS syntax. Install @vitejs/plugin-vue to handle .vue files.
8:25:32 PM [vite] Internal server error: Failed to parse source for import analysis because the content contains invalid JS syntax. Install @vitejs/plugin-vue to handle .vue files.
  Plugin: vite:import-analysis
  File: D:/work/back-end projects/bzxm-wx/BZMobileWeiXin/src/main/webapp/html5/markToMarket/pages/list.vue
  3  |      <a href="chart-1.html" class="list-item">
  4  |        <div class="icon">
  5  |          <i class="iconfont">&#xe643;</i>
     |                                          ^
  6  |        </div>
  7  |        <div class="name">

Enter fullscreen mode Exit fullscreen mode

this is my vite.config.js:

import {resolve} from 'path'
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import mix from 'vite-plugin-mix'

let config = {
  base: './',
  plugins: [
    vue()
  ],
  build: {
    outDir: 'dist',
    rollupOptions: {
      input: {
        index: resolve(__dirname, 'chart-1.html'),
        index2: resolve(__dirname, 'chart-2.html'),
        index3: resolve(__dirname, 'chart-3.html'),
        list: resolve(__dirname, 'index.html')
      }
    }
  }
}

export default defineConfig(function ({ command, mode }) {
  if(mode === 'development'){
    config = Object.assign(config, {
      plugins: [
        mix({
          handler: "./js/utils/api-for-vite.js"
        })
      ]
    })
  }

  return config
})
Enter fullscreen mode Exit fullscreen mode

things also work if i directly export the config without going through defineConfig, but then you get a build folder after the vite build, really weird. 🤣