Projeto de estudos com Electron.js, explorando os conceitos gerais e principais do Electron.js como aplicação Desktop.
- Leitura da documentação original
- Vídeos no YouTube e Sites com artigos (Wiki, Blogs, etc.)
-
Comece com o comando inicial para gerar o
package.jsonnpm init
-
Instale o Electron
npm install electron --save-dev
O
--save-devé comum para ambientes de desenvolvimento, pois o Electron é usado apenas na execução local, não em produção web. -
Crie a estrutura base do projeto:
meu-projeto/ ├── package.json ├── main.js └── index.html
-
Esse comando é um atalho para criar um projeto completo, com estrutura pronta, empacotador, scripts e dependências configuradas:
npx create-electron-app meu-projeto
Ideal para iniciar um projeto profissional sem configurar tudo manualmente.
import { app, BrowserWindow } from "electron";
const criarJanela = () => {
const janela = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true, // permite usar Node.js no front-end
contextIsolation: false // caso precise para o desenvolvimento
}
});
janela.loadFile('index.html');
};
app.whenReady().then(() => {
criarJanela();
});- Detalhe:
contextIsolationé uma configuração isola o contexto de execução do site (renderer) do contexto interno do Electron (Node.js, IPC, etc.). Ele impede que scripts que rodam dentro da página (como o JavaScript do seu index.html ou scripts injetados de terceiros) tenham acesso direto aos recursos internos do Node.js ou do Electron.
- Ao executar, abrirá uma janela automaticamente (em Windows, Linux e macOS).
- O
appcontrola o ciclo de vida da aplicação. - O
BrowserWindowcria e gerencia as janelas da aplicação desktop. - O
BrowserWindowsó pode ser criado após o eventoreadydoappser disparado — por isso usamosapp.whenReady().then().
Execute no terminal npm start para rodar o projeto:
"scripts": {
"start": "electron ."
}app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});- O macOS mantém os apps abertos mesmo sem janelas, por isso o
if. process.platformretorna o sistema operacional atual (win32,linux,darwin...).
app.whenReady().then(() => {
criarJanela();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) criarJanela();
});
});-
app.on()→ Escuta eventos do ciclo de vida do aplicativo, comoready,activate,window-all-closed, etc. -
"activate"→ Evento disparado quando o usuário clica no ícone do app e não há janelas abertas (macOS). -
Separação do código de front-end (
index.html,renderer.js) do código principal (main.js). O processo principal (main.js) controla janelas e sistema. Já o processo de renderização (renderer.js) lida com a interface e interações do usuário. -
Electron junta o Chromium + Node.js, então é possível usar recursos do navegador e do Node ao mesmo tempo.
-
Caso o projeto use módulos ES (import/export), no
package.jsonadicione:{ "type": "module" } -
Por Segurança: Evitar usar
nodeIntegration: trueem produção, pois isso permite execução de scripts maliciosos dentro do DOM. Prefira comunicação segura via IPC (Inter-Process Communication).O que é IPC (Inter-Process Communication)?
É o sistema que permite a comunicação entre o processo principal (´main´, que controla o app e tem acesso ao sistema) e os processos de renderização (
renderer, que exibem a interface (HTML, CSS, JS))Ou seja, o meio pelo qual o front-end (renderer) envia e recebe mensagens do back-end (main) dentro do aplicativo Electron
| Termo / Função | Descrição |
|---|---|
app |
Controla o ciclo de vida da aplicação Electron (inicialização, eventos, fechamento, etc.) |
BrowserWindow |
Cria e gerencia janelas da aplicação, permitindo carregar arquivos HTML ou URLs. |
ipcMain |
Gerencia mensagens vindas do processo de renderização (renderer). |
ipcRenderer |
Envia mensagens do front-end para o processo principal (main). |
webContents |
Representa o conteúdo web renderizado dentro de uma BrowserWindow. |
Tray |
Cria ícones e menus na bandeja do sistema (tray bar). |
Menu |
Cria menus personalizados na janela ou bandeja. |
shell |
Permite abrir links e arquivos externos no sistema operacional. |
app.whenReady() |
Retorna uma Promise resolvida quando o Electron terminou de inicializar. |
app.on('activate') |
Evento disparado quando o aplicativo é ativado (especialmente no macOS). |
app.quit() |
Fecha a aplicação completamente. |
BrowserWindow.getAllWindows() |
Retorna todas as janelas atualmente abertas. |
meu-projeto/
├── api/ # Opcional: sua API (pode ser local ou externa)
│ ├── routes/
│ └── server.js
|
├── main/ # Processo principal (controla janelas, menus, sistema)
│ ├── main.js
│ ├── preload.js # Script que faz ponte entre o main e o renderer (segurança)
│ ├── ipcHandlers.js # Comunicação via IPC
│ └── menu.js # Criação de menus personalizados
│
├── renderer(frontend)/ # Processo de renderização (interface e lógica de UI)
│ ├── index.html
│ ├── renderer.js
│ ├── src/
│ │ ├── App.jsx
│ │ ├── index.jsx
│ │ └── components/ # Componentes de interface (botões, janelas, modais, etc.)
│ ├── public/
│ └── package.json
│
├── assets/
│ ├── icon.png
│ └── logo.svg
│
├── package.json
|
└── build/ # Saída de build (quando empacotar com Electron Builder)
Entendendo o IPC (Inter-Process Communication)
- Arquivo é carregador no rederizador antes do html
- Servem para expor apenas o que você quer para o código do renderizador, sem dar acesso total ao Node.js a forma mais segura.
import { contextBridge } from "electron";
contextBridge.exposeInMainWorld('versions', {
node:() => process.versions.node,
chrome:() => process.versions.chrome,
electron:() => process.versions.electron,
})- Usa o
contextBridgepara criar uma variável global chamadaversionsno renderizador. - Essa variável só permite que leia as versões do Node, Chrome e Electron.
- Não dá acesso completo ao Node.js, o que é importante para segurança.
Ao criar uma janela no Electron, conecta-se o preload
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
// Caso use ES module como forma de importação de módulos, substitua o '__dirname' por 'process.cwd()'
}
})preload.jsé o script que vai rodar antes da página carregar.- Ele injeta a variável
versionsno window da sua página.
No HTML/JS pode-se colocar este trecho para teste.
console.log(window.versions.node()) // mostra a versão do Node
console.log(window.versions.chrome()) // versão do Chrome
console.log(window.versions.electron()) // versão do Electron
- Não deve dar acesso total ao
Node.jsno renderizador por segurança. - O
preload scriptpermite criar um “meio-termo seguro”, onde expõe apenas o que quer do Node/Electron para o renderizador.
O processo principal (main) e o renderizador (Renderer) têm responsabilidades distintas e não são intercambiáveis, ou seja, não épossível acessar as APIs do Node.js diretamente do processo de renderização, nem o HTML (DOM) do processo principal.
A solução para essa comunicação entre processos (IPC), é usar elétrons ipcMain e ipcRenderer. Para enviar uma mensagem da sua página da web para o processo principal, pode configurar um manipulador de processo principal com ipcMain.handle e então exponha uma função que chama ipcRenderer.invoke para acionar o manipulador no script de preload.
import { contextBridge, ipcRenderer } from "electron";
contextBridge.exposeInMainWorld('versions', {
ping: () => ipcRenderer.invoke('ping')
})Observação Importante
Segurança IPC:
- Não exponha todo o 'ipcRenderer' módulo via pré-carregamento para o código da interface. Em vez disso, exponha só funções específicas e controladas. Isso evita que códigos maliciosos dentro da UI mande qualquer mensagem ao processo principal.
Após a configuração do ipcRender deve-se configurar o handle no processo principal (Main), faz isto antes carregando o arquivo html para que o manipulador esteja garantido antes de enviar o invoke da chamada do renderizaor.
import { app, ipcMain } from "electron";
app.whenReady().then(() => {
ipcMain.handle('ping', () => 'pong')
createWindow()
})Depois de configurar o remetente e o destinatário, pode enviar mensagens do renderizador para o processo principal por meio do 'ping' canal que foi definido.
const exibirTexto = async () => {
const response = await window.versions.ping()
console.log(response)
informacao.innerText = `Texto enviado pela Api/Main ${response}`
}
exibirTexto()A mensagem será exibida na janela da aplicação.
Como cada parte se comunica:
🔹 1. Main Process (main.js)
- Roda em Node.js puro.
- Controla janelas (
BrowserWindow), menus, arquivos, etc. - NÃO acessa o DOM, nem React...
- Pode chamar APIs externas usando
fetchouaxios.
🔹 2. Renderer Process (React ou HTML)
- É a interface (React, HTML e CSS).
- NÃO tem acesso direto ao
fs,os, etc. ( Isso por segurança). - Pode pedir para o
Main Processfazer algo viaIPC.
🔹 3. Preload (ponte segura)
- Fica entre o
Maine oRenderer. - Usa
contextBridge.exposeInMainWorld()para expor funções seguras ao React.
Para o frontend usa-se react como exemplo:
function App() {
const enviarForm = async (e) => {
e.preventDefault();
const produto = { nome: "Caneta", preco: 2.50 };
const resultado = await window.electronAPI.cadastrarProduto(produto);
alert(resultado.mensagem);
};
return (
<form onSubmit={enviarForm}>
<input name="nome" placeholder="Nome" />
<input name="preco" placeholder="Preço" />
<button type="submit">Cadastrar Produto</button>
</form>
);
}- O react chama o preload ao executar
window.electronAPI.cadastrarProduto(produto), Isso envia os dados via IPC para o main.js.
O main recebe esses dados via evento IPC e faz requisição na API
import { app, BrowserWindow, ipcMain } from 'electron';
import axios from 'axios';
ipcMain.handle('cadastrar-produto', async (event, dados) => {
try {
const res = await axios.post('http://localhost:3000/api/produtos', dados);
return { sucesso: true, mensagem: 'Produto cadastrado!' };
} catch (erro) {
return { sucesso: false, mensagem: 'Erro ao cadastrar.' };
}
});O Main retorna a resposta da API de volta ao Renderer. O resultado ({sucesso: true, mensagem: 'Produto cadastrado!'}) volta para o React.
Em geral, resumo da comunicação:
[ React (Renderer) ]
↓ (via preload)
[ IPC → Main Process (Electron) ]
↓ (HTTP request)
[ API Express (localhost:3000) ]
↑ (resposta JSON)
[ Main envia de volta via IPC ]
↑
[ Renderer exibe resultado ]
Há duas opções para encaixar a API no projeto.
🔹 1° já existe uma API rodando fora do Electron (como um backend Node.js/Express).
- O electron apenas consome a API
- O Main Process faz as requisições HTTP (axios ou fetch).
- A comunicação é feita pelo ipcMain ↔ ipcRenderer.
🔹 2° Uma API local (dentro do próprio Electron)
- Sobe um servidor Express dentro do main.js, caso quisser tudo embutido.
- Isso torna a aplicação mais pesado.
- Para casos se quer que o app funcione totalmente offline.
Detalhes importante!:
É possível fazer a requisição pelo React (Renderer), mas há pontos importante:
- Fazer pela interface expõe URLs, tokens e possíveis dados sensível.
- Isso burla o isolamento do Electron.
- Caso o app precise funcionar offline, o Main pode lidar com cache e filas, mas Renderer não.
O ciclo de vida é definido quando o app inicia, executa e encerra. No Electron ele é controlado pelo Main process através do módulo app.
- O momento inicialização do ciclo é quando roda o
app.whenReady(). - A execução é quando app está em uso e há interação com as funcionalidades do app.
- O momento de encerramento é quando todas as janelas são fechadas o electron emite um evento. Há eventos que permitem interceptar o momento antes do app encerrar chamados
before-quitewill-quit, eles podem salvar dados do usuário, fechar conexões com banco de dados e cancelar saídas indevidas.
Acesse os Readmes
