Creating a Java + Electron + React Typescript desktop app

Create react app

{
"ignorePatterns": ["*.html"]
}
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"printWidth": 100
}
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"pretty": "prettier --write \"./**/*.{js,jsx,json,ts,tsx}\"",
}

Convert the app to Electron

{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"sourceMap": true,
"strict": true,
"outDir": "../build",
"rootDir": "../",
"noEmitOnError": true,
"typeRoots": [
"node_modules/@types"
]
}
}
import { app, BrowserWindow } from 'electron';
import * as path from 'path';
import * as isDev from 'electron-is-dev';
import installExtension, { REACT_DEVELOPER_TOOLS } from "electron-devtools-installer";

let win: BrowserWindow | null = null;

function createWindow() {
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})

if (isDev) {
win.loadURL('http://localhost:3000/index.html');
} else {
// 'build/index.html'
win.loadURL(`file://${__dirname}/../index.html`);
}

win.on('closed', () => win = null);

// Hot Reloading
if (isDev) {
// 'node_modules/.bin/electronPath'
require('electron-reload')(__dirname, {
electron: path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron'),
forceHardReset: true,
hardResetMethod: 'exit'
});
}

// DevTools
installExtension(REACT_DEVELOPER_TOOLS)
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log('An error occurred: ', err));

if (isDev) {
win.webContents.openDevTools();
}
}

app.on('ready', createWindow);

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

app.on('activate', () => {
if (win === null) {
createWindow();
}
});
"homepage": ".",
"main": "build/electron/main.js",
"author": "Your Name",
"description": "React-TypeScript-Electron sample with Create React App and Electron Builder",
"build": {
"extends": null,
"files": [
"**/*",
"build/**/*"
],
"directories": {
"buildResources": "assets"
},
"extraResources": [
{
"from": "backend",
"to": "."
}
]
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"pretty": "prettier --write \"./**/*.{js,jsx,json,ts,tsx}\"",
"postinstall": "electron-builder install-app-deps",
"electron:dev": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && tsc -p electron -w\" \"wait-on http://localhost:3000 && tsc -p electron && electron .\"",
"electron:build": "yarn build && tsc -p electron && electron-builder"

},

Make a Spring Boot Backend

Create an API and hook up to a database

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
package com.demo.backend.api;

import com.demo.backend.dto.Person;
import com.demo.backend.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api")
public class PersonController
{
private final PersonService personService;

@Autowired
private PersonController(PersonService personService)
{
this.personService = personService;
}

@GetMapping("/people")
public ResponseEntity<List<Person>> getPeople()
{
return new ResponseEntity<List<Person>>(personService.getAllPeople(), HttpStatus.OK);
}

@PostMapping("/person")
public ResponseEntity<String> addPerson(@RequestBody Person person)
{
personService.addPerson(person);
return new ResponseEntity<>(HttpStatus.CREATED);
}
}
package com.demo.backend.dto;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity
@Table(name = "people_details")
public class Person
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;

@Column(name = "first_name")
private String firstName;

@Column(name = "last_name")
private String lastName;

@Column(name = "age")
private int age;

@Column(name = "address")
private String address;
}
package com.demo.backend.repository;

import com.demo.backend.dto.Person;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PersonRepository extends CrudRepository<Person, Integer>
{
}
package com.demo.backend.service;

import com.demo.backend.dto.Person;
import com.demo.backend.repository.PersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class PersonService
{
private final PersonRepository personRepository;

@Autowired
private PersonService(PersonRepository personRepository)
{
this.personRepository = personRepository;
}

public List<Person> getAllPeople()
{
List<Person> people = new ArrayList<>();
personRepository.findAll().forEach(people::add);
return people;
}

public void addPerson(Person person)
{
personRepository.save(person);
}
}
import { app, BrowserWindow } from 'electron';
import * as path from 'path';
import * as isDev from 'electron-is-dev';
import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer';
import { ChildProcessWithoutNullStreams } from 'child_process';

let win: BrowserWindow | null = null;
let child: ChildProcessWithoutNullStreams;

function createWindow() {
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
},
});


if (isDev) {
win.loadURL('http://localhost:3000/index.html');
} else {
// 'build/index.html'
win.loadURL(`file://${__dirname}/../index.html`);
// spawn java child process running backend jar
const jarPath = path.join(process.resourcesPath, '/backend-0.0.1-SNAPSHOT.jar';
child = require('child_process').spawn( 'java', ['-jar', jarPath, ''] );

}

win.on('closed', () => (win = null));

// Hot Reloading
if (isDev) {
// 'node_modules/.bin/electronPath'
require('electron-reload')(__dirname, {
electron: path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron'),
forceHardReset: true,
hardResetMethod: 'exit',
});
}

// DevTools
installExtension(REACT_DEVELOPER_TOOLS)
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log('An error occurred: ', err));

if (isDev) {
win.webContents.openDevTools();
}
}

app.on('ready', createWindow);

app.on('window-all-closed', () => {
if (child) {
const kill = require('tree-kill');
kill(child.pid);
}

if (process.platform !== 'darwin') {
app.quit();
}
});

app.on('activate', () => {
if (win === null) {
createWindow();
}
});

Package the electron installer

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store