Back to Articles

Learn how to build a desktop application with Electron and Next.js while also adding API route capabilities.

Faiz Khan

Faiz Khan

Posted At: 7/28/2024

This article is written as a continuation of this wonderful article that teaches you how to bundle your nextjs app with Electron so you can share the .exe file without compromising your code.

BACKSTORY

A little while ago, I was looking for some direction to package my nextjs application to .exe. The goal was simple: share a fully functional desktop application with my clients without exposing the source code. This process proved particularly challenging due to the dynamic and backend-dependent nature of my application, which extensively utilizes Next.js API routes. I came across the article which I mentioned above that exports the static HTML, CSS, to build an output which Electron renders. But this is only useful when you have a static website or you don’t have a backend route in nextjs.

In his article, he mentioned this:

'''BUT, maybe there’s a solution for those problems. NextJS has a possibility to use custom servers (as described here, on official documentation), but it may be more difficult to integrate with Electron in a good way, and also it will make your app more heavy, since it will truly serve your application (it will run a NodeJS server behind the scenes) on your end-user computer, in addition to enable your users to explore your front-end outside your desktop app (for example, NextJS will run on localhost:3000 and your Electron will render that URL on the BrowserWindow, but it allows your users to open a browser — Chrome, Firefox, Edge etc — and access localhost:3000 directly).'''

I was really intrigued by this approach (also I needed to do it or else I would have to spend the majority of my day explaining to clients the number of environments they need to set up to run the code 😪.).

So basically this guide is helpful for those who are using APP route nextJS and need to have a backend functionality work on Desktop Application.

LET’S START

Before continuing, I’m assuming that you have already setup the nextjs application and have bundled electron application. If not, then follow this article.

STEP!: Ceate Your Server File.

Begin by creating a server.js file at the root of your project. This server will handle both your API routes and serve your static files. Here's how you can set it up:

const path = require('path')
require('dotenv').config({ path: path.join(__dirname, '.env') })
const express = require('express')
var cors = require('cors')
const app = express()
app.use(express.json())
app.use(cors())
const PORT = 3000

app.get('/api/v1/:id', async (req, res) => {
   // Implementation of GET route
  });
  
  app.post('/api/v1/', async (req, res) => {
   // Implementation of POST route
  });

// Serve static files from the Next.js export
app.use(express.static(path.join(__dirname, './out')))
// Catch-all to serve index.html for any other requests
app.get('*', (req, res) => {
 res.sendFile(path.resolve(__dirname, './out', 'index.html'))
})
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`))

The code is pretty self-explanatory. You need to have express js, cors and dotenv installed as a dependency.
require(‘dotenv’).config({ path: path.join(__dirname, ‘.env’) }) will basically takes the secret that you might have in .env file.


There are 2 routes that I have defined in my server file. Whatever routes you might have defined in your nextjs api route, you need to convert it as your express api route because now we will be running out application using custom express js server.

One more point to be mention is that we will be serving our contents from out folder that was created from npm run build and will be using the api routes from express js.

Step 2: Configure Electron

Adjust your Electron setup to interact with this server:

const { app, BrowserWindow } = require('electron')
const path = require('path')
let mainWindow


const createWindow = () => {
 mainWindow = new BrowserWindow({
  width: 1920,
  height: 1080,
  webPreferences: {
   preload: path.join(__dirname, 'preload.js'),
   nodeIntegration: false,
   contextIsolation: true,
   webSecurity: false,
  },
 })
 mainWindow.loadURL('http://localhost:3000')
}
app.on('ready', () => {
 require('../server')
 createWindow()
})
app.on('window-all-closed', () => {
 if (process.platform !== 'darwin') {
  app.quit()
 }
})

Remember to add require(‘../server’) in your ready callback function.


NOTE: — You might still face problem in facing the value from .env. If you dont have any .env secrets then it’s ok but if you want to fetch those values from .env, you can copy the follwoing code to your package.json .

{
 "build": {
    "extraFiles": [
      {
        "from": ".env",
        "to": "./",
        "filter": [
          "**/*"
        ]
      }
    ]
  }
}

And you are all set. You dont even need to change your frontend . In order to start your electrom application and leveraging the api capability, just followthese steps:

  • npm run build: to build a static file
  • node server.js : to start the server
  • npm run electron-dev : to run an electron desktop application

If you want to bundle your application to let’s say .exe, just follow the usual steps, i.e:

  • npm run build : to build a static file
  • npm run electron-build : to bundle an electron application

CONCLUSION

And this is it. This is how I was saved from the frustration of my client and able to share the .exe file across the clients and all of its dynamic functionality.

Integrating Electron with Next.js using a custom server approach not only preserved the dynamic functionalities of my application but also streamlined the deployment process. This method proved invaluable, sparing me the tedious explanations often required when setting up multiple environments for client use. Now, sharing a dynamic, fully functional desktop application with clients is as simple as distributing a .exe file.