O Electron se destacou muito nos últimos tempos por oferecer a possibilidade de construirmos aplicações desktop utilizando tecnologias web.
As aplicações construídas com Electron podem funcionar Offline, acessar os Recursos Nativos do Sistema Operacional e até mesmo serem comercializadas nas lojas de aplicativos.
Hoje, vamos aprender a desenvolver uma aplicação desktop completa, com Electron e React Js.
Passo #1: Criação do projeto
Para começamos, criaremos um projeto através do Create React App.
Você pode criar seu projeto através do comando abaixo:
create-react-app app
Com o processo finalizado, você pode entrar na pasta app que você criou e rodar o comando:
yarn start
Após alguns segundos, uma nova janela no seu navegador se abrirá, semelhante a essa:

Passo #2: Instalação das bibliotecas
Agora, instalaremos as bibliotecas que usaremos no nosso projeto.
Começaremos instalando o electron, o wait-on e a biblioteca concurrently como dependências de desenvolvimento.
Para isso, dentro da pasta do seu projeto, rode o comando abaixo:
yarn add electron wait-on concurrently -D
O concurrently é biblioteca que nos permitirá rodar vários comandos em sequência.
O wait-on nos permitirá esperar até que a página web da nossa aplicação esteja carregada, para assim, iniciarmos nosso app.
Também precisamos instalar a biblioteca electron-is-dev como uma dependência de produção.
Você pode fazer isso com o comando abaixo:
yarn add electron-is-dev
Instalaremos algumas dependências extras no Passo #6: Build do Projeto.
Passo #3: Configuração do Electron
Agora, precisamos fazer algumas configurações para que o Electron funcione corretamente.
Para isso, crie um arquivo electron.js na pasta public do seu projeto.
Esse arquivo precisa conter o seguinte conteúdo:
const electron = require('electron');
const { app } = electron;
const { BrowserWindow } = electron;
const path = require('path');
const isDev = require('electron-is-dev');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
},
});
mainWindow.loadURL(
isDev ? 'http://localhost:3000' : `file://${path.resolve(__dirname, '..', 'build', 'index.html')}`,
);
if (isDev) {
mainWindow.webContents.openDevTools();
}
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
A função createWindow é responsável por criar uma janela no desktop, chamada BrowserWindow.
O BrowserWindow pode receber alguns parâmetros, como a largura (width) e a altura (height) em que a janela iniciará.
A opção nodeIntegration: true nos permite acesso à API nativa do electron, é importante que essa opção esteja ativada para a aplicação que vamos construir nesse post.
Agora, precisamos apenas criar o nosso script de desenvolvimento, para isso, adicione o script abaixo no campo scripts do seu package.json:
"dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron public/electron.js\""
Nesse momento, o electron está configurado e pronto para uso.
Sua estrutura de pastas deve estar semelhante a essa:

Para ver aplicação em modo de desenvolvimento, basta rodar o comando abaixo:
yarn dev
Ao rodar o comando acima, uma nova janela será aberta na sua máquina, semelhante a essa:

Passo #4: Configurações gerais da aplicação
Para criar o projeto desse post, você também precisará instalar o syled-components, para isso, basta rodar o comando abaixo:
yarn add styled-components
Em todos os projetos que eu crio utilizando Create React App, eu sempre gosto de fazer algumas configurações para deixar o projeto mais organizado.
Começaremos apagando alguns arquivos na pasta src, são eles:
- App.css
- App.test.js
- index.css
- logo.svg
- serviceWorker.js
Apague também os seguintes arquivos da pasta public:
- logo192.png
- logo512.png
- manifest.json
- robots.txt
Precisamos também remover a importação desses arquivos.
O seu index.js da pasta src deve ficar da seguinte forma:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
E o seu arquivo App.js da pasta src deve ficar da seguinte forma:
import React from 'react';
function App() {
return <div />;
}
export default App;
Nesse momento, sua estrutura deve estar mais ou menos assim:

Passo #5: Construção da aplicação
Agora, iremos dar início a construção da nossa aplicação.
Criaremos uma Todo List, semelhante ao Google Keep.
Para isso, crie uma pasta pages dentro da pasta src do seu projeto.
Dentro da pasta pages, crie uma nova pasta Main, e nela, crie dois novos arquivos, o index.js e o styles.js
No arquivo index.js, adicione o seguinte conteúdo:
import React, { useState, useEffect } from 'react';
import crypto from 'crypto';
import {
Container,
NewTodo,
NewTodoInput,
TodosList,
Todo,
TodoName,
Button,
AddTodoMessage,
} from './styles';
const { ipcRenderer } = window.require('electron');
export default function Main() {
const [todos, setTodos] = useState([]);
function handleNewTodo(e) {
e.preventDefault();
const newTodo = e.target.todo.value;
if (!newTodo) return;
const todo = {
id: crypto.randomBytes(256),
name: newTodo,
};
setTodos([...todos, todo]);
const message = {
title: 'Um novo todo foi criado',
body: `O todo ${todo.name} foi criado`,
};
ipcRenderer.send('@notification/REQUEST', message);
e.target.reset();
}
function handleRemoveTodo(todoId) {
setTodos(todos.filter(todo => todo.id !== todoId));
}
function renderTodos() {
return todos.map(todo => (
<Todo key={todo.id}>
<TodoName>{todo.name}</TodoName>
<Button onClick={() => handleRemoveTodo(todo.id)}>Concluir</Button>
</Todo>
));
}
function handleNotificationFailure(message) {
console.log(message);
}
useEffect(() => {
ipcRenderer.on('@notification/FAILURE', handleNotificationFailure);
return () => {
ipcRenderer.removeListener('@notification/FAILURE', handleNotificationFailure);
};
}, []);
return (
<Container>
<NewTodo onSubmit={handleNewTodo}>
<NewTodoInput name="todo" placeholder="Novo todo" />
<Button>Adicionar</Button>
</NewTodo>
{todos.length > 0 ? (
<TodosList>{renderTodos()}</TodosList>
) : (
<AddTodoMessage>Adicione um novo todo :D</AddTodoMessage>
)}
</Container>
);
}
Nesse arquivo, criamos uma página de criação de todos, que salva os todos em uma variável, através do state.
A função handleNewTodo é responsável por criar um novo todo na nossa lista atual, e em seguida, envia uma mensagem à API Nativa do Electron.
Essa mensagem contém um tipo (@notification/REQUEST) e os detalhes dessa mensagem, contidos na variável message.
Utilizamos o useEffect para definirmos um listener caso a criação da mensagem falhe, e nesse caso, chamamos a função handleNotificationFailure onde damos um console na mensagem de erro.
Temos também a função handleRemoveTodo que remove o todo quando o usuário clica em Concluir.
Agora, criaremos os estilos dessa página, utilizando o styled-components.
Para isso, no arquivo styles.js, adicione o seguinte conteúdo:
import styled from 'styled-components';
export const Container = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
`;
export const NewTodo = styled.form`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
margin: 0 auto;
padding: 50px 0;
text-align: center;
`;
export const NewTodoInput = styled.input`
height: 50px;
padding: 0 15px;
margin: 10px;
border: 0.5px solid #333;
border-radius: 4px;
font-size: 14px;
`;
export const TodosList = styled.div`
display: flex;
justify-content: center;
flex-wrap: wrap;
list-style: none;
margin-top: 50px;
`;
export const Todo = styled.div`
width: 250px;
max-width: 250px;
border: 1px solid #eee;
padding: 15px 20px;
text-align: left;
margin: 10px;
`;
export const TodoName = styled.h3`
font-size: 16px;
margin-bottom: 5px;
color: #333;
`;
export const Button = styled.button`
width: 100%;
max-width: 250px;
height: 50px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.05);
border: 0;
border-radius: 4px;
background: #009cde;
color: #fff;
font-weight: bold;
cursor: pointer;
`;
export const AddTodoMessage = styled.h1`
color: #333;
`;
Com os estilos da nossa página criados, criaremos agora o código de exibição de uma mensagem nativa quando um todo for criado.
Nesse código, faremos com que seja exibido uma notificação sempre que um novo todo for criado.

Para isso, começaremos a utilizar a API Nativa que o Electron nos oferece.
Começaremos criando uma pasta chamada app na raiz do projeto (fora da pasta src).
Dentro da pasta app, crie um arquivo index.js
No arquivo index.js, adicione o seguinte conteúdo:
const { ipcMain, Notification } = require('electron');
ipcMain.on('@notification/REQUEST', async (event, message) => {
try {
const { title, body } = message;
const notification = new Notification({
title,
body,
});
notification.show();
} catch (err) {
event.sender.send('@notification/FAILURE', 'Houve um erro na criação da notificação');
}
});
Essa parte do projeto segue bastante a ideia do websocket, onde enviamos uma mensagem sempre que quisermos nos comunicar com a API Nativa do Electron.
Nesse arquivo, nós estamos ouvindo a mensagem do tipo @notification/REQUEST, e esperamos receber o parâmetro message.
O event é enviado por padrão para todos os listeners, contendo as informações do evento e de quem o disparou.
Ao receber a mensagem do tipo que definimos (@notification/REQUEST), nós instanciamos um objeto do tipo Notification e utilizamos o método show para mostrar a mensagem em tela.
No catch dessa função, nós enviamos uma mensagem para sender informando que houve um erro.
Agora, precisamos importar esse arquivo dentro do arquivo public/electron.js que criamos no Passo #3: Configuração do Electron.
Para isso, adicione a seguinte linha logo após o require do electron:
require('../app');
Precisamos importar também a página Main que criamos no Passo #5: Construção da aplicação.
Para isso, deixe o conteúdo do arquivo App.js da pasta src da seguinte maneira:
import React from 'react';
import Main from './pages/Main';
function App() {
return (
<>
<Main />
</>
);
}
export default App;
Nesse momento, sua estrutura de pastas deve estar semelhante a essa:

OBS.: É importante que você pare seu servidor o rode o comando yarn dev sempre que houver alguma alteração nos arquivos relacionados ao Electron.
Demonstração

Passo #6: Build do Projeto
Para fazer o build do projeto precisamos seguir alguns passos importantes.
Primeiramente, precisamos instalar as dependências necessárias para que o build do projeto seja feito corretamente.
Para instalar as bibliotecas, utilize o comando abaixo:
yarn add @rescripts/cli @rescripts/rescript-env electron-builder typescript -D
Precisaremos também fazer algumas alterações nas configurações do webpack do nosso projeto.
Para isso, utilizaremos a biblioteca rescripts para fazermos essas alterações sem precisar ejetar as configurações do Create React App.
O electron-builder é a biblioteca sugerida pelo próprio electron para o build de projetos.
Para começarmos a fazer essas configurações, crie um arquivo na raiz do seu projeto chamado .webpack.config.js e adicione o seguinte conteúdo:
module.exports = config => {
config.target = "electron-renderer";
return config;
};
Em seguida, precisamos fazer o rescripts entender essas configurações.
Para isso, também na raiz do seu projeto, crie um arquivo .rescriptsrc.js e adicione à ele o seguinte conteúdo:
module.exports = [require.resolve("./.webpack.config.js")];
Agora, precisamos definir algumas informações referentes ao criador do software.
Essas informações são muito importantes, principalmente se você quiser disponibilizar seu software nas lojas de Aplicativos, como a Windows Store ou a App Store.
Para isso, adicione o código abaixo no seu package.json:
"author": {
"name": "Seu nome",
"email": "seu.email@email.com",
"url": "https://seu-site.com"
},
"homepage": "./",
"main": "public/electron.js",
"build": {
"appId": "com.seu-site.seu-app",
"productName": "MyApp",
"copyright": "Copyright © 2019 Seu Nome",
"mac": {
"category": "public.app-category.utilities"
},
"files": [
"build/**/*",
"node_modules/**/*"
],
"directories": {
"buildResources": "assets"
}
},
Agora, precisamos alterar os scripts da nossa aplicação para que eles utilizem o rescripts em vez do react-scripts.
Além disso, criaremos também os scripts de build da nossa aplicação, para isso, os scripts do seu package.json devem ficar da seguinte maneira:
"scripts": {
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",
"eject": "react-scripts eject",
"dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\"",
"postinstall": "electron-builder install-app-deps",
"preelectron-build": "yarn build",
"electron-build": "electron-builder -mwl"
},
Nós criamos 3 scripts novos, são eles:
- postinstall: Esse script irá instalar as dependências do electron builder sempre que rodarmos o comando install. Esse script é uma recomendação do electron-builder para que as dependências dele estejam sempre atualizadas.
- electron-build: Esse script fará o build do nosso projeto. Nele, podemos passar algumas flags para informamos para quais plataformas o projeto deve ser buildado. Nesse caso, passamos a flag -mwl para informamos que o build deve ser para o MacOS (m), Windows (w) e Linux (l).
- preelectron-build: Esse script irá buildar o nosso projeto CRA e gerar os arquivos estáticos. Como criamos esse script com o pre antes do electron-build, ele sempre irá executar antes do electron-build.
Com tudo isso configurado, rode o comando abaixo:
yarn electron-build
Quando rodarmos esse comando, o electron-builder irá gerar os arquivos executáveis do nosso projeto.
O processo de build pode demorar alguns minutos, principalmente se você está gerando o build para várias plataformas.
Os arquivos gerados no processo de build podem ser encontrados na pasta dist.
Ao finalizar e buildar sua aplicação, sua estrutura de pastas deve estar semelhante a essa:

Concluindo
Nesse post pudemos ver um pouco do poder do Electron. Aprendemos também como realizar o build de projetos criados com Electron e React Js.
Espero que esse post tenha te ajudado de alguma forma 🙂
Forte Abraço,
Carlos Levir
Levir, pensando em desenvolver uma aplicação com reactjs e node, rodando localmente pra um cliente, teria como fazer alguma aplicação com o electron, onde a gente colocasse o caminho do frontend e backend com um botão de “run” ao lado pra startar as aplicações? Até porque o cliente não vai abrir o projeto e rodar “yarn start”
Faaala Marcelo, isso não é necessário, quando o projeto é buildado, já é gerada uma versão de produção utilizável, ou seja, a aplicação já vai com “yarn start” ativo digamos assim.
Grande Abraço 😀
levir, uso React diariamente no meu trabalho e estava pensando em aprender a usalo com electron, no caso eu já fiz uma aplicação com electron porem utilizando angular e nesta aplicação eu estava utilizando o sqlite como banco imbutido. Caso saiba como poderia escrever um artigo sobre como implementar o uso do sqlite com o React e electron. ?
Faaaala Fernando, muito obrigado pela sugestão 🙂
Grande Abraço 😀
Falaaaaa. Então, após buildar, as telas são renderizadas mesmo sem estar com o servidor do react ativo?
Fala Maxwell, sim, após o build são gerados arquivos estáticos 🙂
Muito obrigado pelo post. Aprendi bastante com ele. Seria interessante para um próximo realizar a integração com o sqlite.
Opa João Victor, valeu pela sugestão 🙂
Fala, Levir.
Tô passando só pra te agradecer pela grande postagem. Muito bem explicada e direto ao ponto.
Parabéns pelo trabalho.
Muito bom o tutorial! Mas eu tenho uma grande duvida… Qual a melhor opção para um banco de dados LOCAL que pode ate ser instalando junto quando geramos o instalador do sistema feito em Electron?
Você pode usar o SQLite 😉