Como montar um sistema de faturas e contas usando só o navegador

Faturas que saem, contas que entram e uma tela que diz o que vence esta semana — do fork no GitHub a um container com PostgreSQL gerenciado, do primeiro clique a backup, restauração e atualizações, sem abrir um terminal.

Tela de um app de faturas intitulada Esta semana, 12 a 19 de outubro de 2026: cartões com $858.60 a receber, sete faturas atrasadas de $5,326.50, $328.00 a pagar, três contas atrasadas de $1,020.00 e $8,575.40 recebidos nos últimos sete dias, acima de duas tabelas de faturas e contas que vencem esta semana

Tradução do original em inglês, testado em 12 de outubro de 2026. Código, números e datas são os do original. Read in English

Se você tem uma pequena empresa, faturas saem, contas entram, e a pergunta da manhã de segunda é sempre a mesma: o que vence esta semana, e quem ainda me deve? Uma planilha responde até a terceira pessoa editá-la. Serviços de faturamento respondem por uma mensalidade e guardam seus dados do lado deles.

Este post é a terceira opção: um app que é seu, numa hospedagem que custa US$ 10 por mês, montado usando só o navegador. Você faz o fork de um repositório, cria um container com o banco de dados ligado, abre a URL e começa. Sem terminal, sem editor de código, sem instalar nada no seu computador.

Teste, faça o deploy, leia: a demo ao vivo está em firmbookenvtest-07gb19sd.b4a.run — entre com [email protected] / acme-demo-2026, somente leitura, dados fictícios. O template está em github.com/templates-back4app/firmbook.

Tudo abaixo foi feito de 9 a 12 de outubro de 2026, na plataforma Back4app Containers, e cada número vem dessas rodadas. Os logs estão no repositório, em docs/notes/ e docs/evidence/.

Para quem é, e o que o app faz

O leitor que tínhamos em mente tem uma loja, um estúdio ou um consultório, emite algumas dezenas de faturas por mês e consegue seguir instruções numa página web. O app é deliberadamente pequeno, para ser entendido em uma tarde:

  • Contatos — clientes e fornecedores numa lista só, com o endereço impresso nas faturas.
  • Faturas — itens, quantidade, preço unitário, uma alíquota de imposto que é somada aos preços ou extraída deles, numeração sequencial por ano (INV-2026-0001), uma página para impressão e um link de compartilhamento.
  • Pagamentos — dinheiro recebido, aplicado a uma fatura ou mantido como crédito; pagamentos parciais, pagamentos a maior e reembolsos tratados com honestidade (mais sobre isso abaixo).
  • Contas — o que você deve, com vencimentos, categorias e pagamentos.
  • This week — dinheiro a receber e a pagar nos próximos sete dias, o que está atrasado, o que entrou e saiu nos últimos sete.
  • Configurações — dados da empresa, imposto, pessoas (dono, equipe, demo somente leitura), dados de exemplo, backup, restauração, exportação CSV e um registro de quem fez o quê.

Não é um software de contabilidade. Há uma moeda só, sem partidas dobradas, sem integração bancária, sem declaração de impostos. Quando você crescer além dele, os dados saem num único arquivo.

A tela This week do Firmbook: cartões de dinheiro a receber e a pagar, totais atrasados e caixa recebido, acima de duas tabelas de faturas e contas que vencem esta semana

A stack, e por que é essa

Para um leitor que nunca vai abrir o código, queríamos o mínimo de peças móveis que ainda sobrevivem ao uso real: um processo Node.js (Express, páginas renderizadas no servidor, nenhum framework JavaScript para fazer build), um banco PostgreSQL (o add-on que o Back4app Containers oferece no formulário de criação) e um repositório GitHub que o leitor possui.

A Back4app faz o build com o Node.js Buildpack — a dashboard lê o package.json, vê "engines": { "node": ">=22" } e o script start, e constrói a imagem. Não há Dockerfile no repositório, e nada para configurar em Build & run.

Dois detalhes da plataforma decidiram o design, e vale conhecer os dois antes de clicar:

Deploy clique a clique

1. Faça o fork do template. Em github.com/templates-back4app/firmbook, clique em Fork. Não em Use this template: um fork mantém o vínculo com o original, e é isso que permite ao botão Sync fork do GitHub trazer atualizações depois.

O fork de um repositório público é público. O repositório não guarda nenhum dado do negócio, e a tabela mais abaixo diz onde vive cada coisa privada.

2. Crie o app. Na dashboard da Back4app, abra Containers → Deploy a web app, escolha GitHub repository e selecione o seu fork. O formulário detecta Node.js Buildpack · Node.js >=22, a porta 8080 e o comando de start; o health check é /healthz.

3. Ligue o banco de dados e digite a frase secreta. No mesmo formulário, em Database, ligue PostgreSQL · 1 GB; o plano muda para Starter, porque o plano Free não pode ter banco de dados.

Em Environment variables, adicione SETUP_SECRET com uma frase de pelo menos cinco palavras sem relação entre si, inventadas por você, ou um valor do seu gerenciador de senhas. Clique em Create app.

4. Abra a URL assim que ficar verde. O primeiro build levou 147 segundos na nossa rodada (ainda não há cache). A página de primeira execução do app pede a frase secreta e cria a conta do dono — ou restaura um backup de outro deployment. Ela mostra oito códigos de recuperação de uso único: guarde-os.

A página de primeira execução de um deployment novo: crie a conta do dono com a frase secreta de setup, ou restaure um arquivo de backup

5. Configurações, depois dados de exemplo. Preencha nome da empresa, endereço, rótulo e alíquota do imposto, e se os preços que você digita já incluem imposto.

Clique em Add sample data para ver o app com 24 faturas, pagamentos e contas; clique em Remove sample data quando começar de verdade — ele apaga só as linhas que marcou, e se recusa se algum registro real apontar para um de exemplo.

Por que a frase secreta, afinal? Porque uma URL não publicada não é um segredo: hostnames novos aparecem nos logs públicos de certificados em minutos, e o primeiro visitante de um app vazio viraria o dono.

A frase é consumida na mesma transação de banco que cria o dono, então duas tentativas simultâneas não podem ter sucesso as duas — disparamos dez de uma vez e obtivemos um dono e nove recusas.

Crie um contato e depois uma fatura: descrição, quantidade, preço unitário por linha, data de vencimento (em branco significa 14 dias após a emissão).

Os preços são sem imposto por padrão; com a configuração prices include tax, o app armazena líquido = round(bruto ÷ (1 + alíquota)) por linha, de modo que o total do cliente é exatamente o que você digitou. Três linhas a US$ 19,99 com 10 % de imposto incluído dão US$ 59,97, com US$ 5,46 de imposto.

Emitir congela a fatura: ela recebe o próximo número do ano dentro da mesma transação (dez faturas emitidas simultaneamente receberam dez números consecutivos) e um snapshot do nome e do endereço do contato, para que edições posteriores no contato nunca alterem uma fatura emitida. Uma fatura emitida nunca é editada: ela é anulada e uma nova é emitida.

Uma fatura emitida: cabeçalho da empresa, bloco de cobrança, três linhas, subtotal, imposto sobre vendas e total, com os botões Record a payment, Mark as sent e Create share link

Registre um pagamento pela página da fatura. Um pagamento é um movimento de caixa; o que o liga a faturas é uma alocação separada. É essa distinção que mantém os números honestos: um pagamento de US$ 120 numa fatura de US$ 100 marca a fatura como paga e deixa US$ 20 de crédito para o cliente.

Aplicar esse crédito à próxima fatura não cria dinheiro novo, e a lista Payments continua dizendo US$ 120 recebidos. Anular uma fatura que tem pagamentos move essas alocações para a substituta ou para crédito — o dinheiro nunca desaparece de um relatório.

Todo formulário carrega uma chave de uso único, então um clique duplo ou um envio repetido não registra nada duas vezes (cinco envios simultâneos de um mesmo formulário de pagamento produziram um pagamento).

Link de compartilhamento. Cada fatura emitida pode receber um link com um token aleatório de 32 bytes. A página mostra a fatura e um botão de impressão, nada mais; ela envia X-Robots-Tag: noindex, Referrer-Policy: no-referrer e Cache-Control: no-store, não carrega nenhum asset de terceiros e morre quando você o revoga.

Não há envio de e-mail nesta versão: você cola o link ou imprime em PDF, depois clica em Mark as sent.

A página pública de compartilhamento de uma fatura: a folha da fatura e um botão Print / save as PDF, sem navegação

O que é seu, e onde fica

Tudo de que o negócio depende está no seu fork ou no seu banco de dados, e os dois saem com você: o fork por clone, o banco como um arquivo de backup ou uma exportação CSV. A tabela diz onde cada coisa vive e quem pode lê-la.

O quêOndeQuem pode ler
Seu fork do códigoGitHub, públicotodo mundo — não guarda dados do negócio
Dados da empresa, imposto, rodapé da faturao banco do app (Configurações)as pessoas que você adiciona ao app
Logo, cores, rótuloscustom/ no seu fork, públicotodo mundo
Contatos, faturas, pagamentos, contasPostgreSQL na Back4appo app; o navegador de tabelas da dashboard; seu arquivo de backup
Senhascom hash (scrypt) no banconinguém — um reset exige um código de recuperação ou a dashboard
Sessões, chaves de uso únicoo banco, nunca exportadaso app

A página Database da dashboard é um navegador de tabelas de verdade: tabelas, colunas, índices, um console SQL e uma exportação CSV da página que você está vendo. O que ela não tem, em outubro de 2026, é um botão de backup ou snapshot para o add-on. Seu caminho de recuperação é o arquivo de backup do próprio app.

Backup e restauração, cronometrados

Settings → Download backup gera um zip com cada tabela em JSON, tirado numa única transação repeatable read (tiramos um enquanto oito pagamentos eram registrados em paralelo; toda alocação no arquivo apontava para um pagamento no mesmo arquivo). Sessões, chaves de uso único e tokens de reset nunca entram. Trate o arquivo como um extrato bancário.

Restore fica em dois lugares: na página de primeira execução de um deployment vazio e em Settings, para o dono, onde substitui tudo e desconecta todo mundo. Um backup feito por uma versão mais nova do que o app em que você restaura é recusado, com a indicação da versão a instalar primeiro.

No app ao vivo: um backup com 1.024 faturas, 3.072 linhas e 2.024 pagamentos tinha 155.388 bytes e levou 2,2 s; restaurar o arquivo de 248 linhas de antes dessa rodada levou 714 ms.

Configurações: a seção Backup and restore com Download backup, Export CSVs e o formulário Restore

Atualizações, e o que “voltar” significa

Atualizar é Settings → Download backup, depois Sync fork → Update branch na página do seu fork no GitHub, depois esperar o deploy automático (68 s do push até uma versão nova respondendo, nas nossas rodadas — ligue Autodeploy em Build & deploy; um app criado no plano Free vem com ele desligado).

Suas personalizações vivem em custom/ — logo, tema, rótulos —, uma pasta que nenhuma versão toca, então elas não podem conflitar com uma atualização. Se você editou qualquer outro arquivo, o GitHub pode reportar um conflito e oferecer o editor web; além dos casos simples, isso é trabalho de desenvolvedor, e o artigo diz isso em vez de fingir o contrário.

Se a versão nova não sobe, o app mostra uma página simples de temporarily unavailable — sem texto de erro, sem logs — e grava o motivo no log de runtime que você lê na dashboard. Todas as migrações de uma versão rodam em uma transação, então uma que falha deixa o banco exatamente como estava.

Instant Rollback na dashboard então inicia de novo a imagem anterior; o diálogo diz, com suas próprias palavras, que o banco de dados não é revertido. Medimos 7 s para voltar e 3 s para avançar de novo.

Senha perdida, tudo perdido

Um código de recuperação redefine a senha do dono e desconecta todas as outras sessões. Se os códigos também se perderam, a tela Lost everything? da página de login mostra um código aleatório novo e pede para você colá-lo na dashboard como OWNER_RESET_TOKEN e fazer o deploy.

O app só aceita um código que chega pelo ambiente, uma vez, em até 30 minutos depois desse deploy. Saber o e-mail do dono não vale nada sozinho — isso é um teste no repositório, não uma promessa.

Uma ressalva decorre do achado acima: esse fluxo depende de uma variável adicionada depois da criação chegar ao container, o que falhou em um dos nossos dois apps. Até que isso seja resolvido do lado da plataforma, os códigos de recuperação são o caminho confiável.

Escala, latência e tamanho

Enchemos o app ao vivo pelos seus próprios formulários — 1.000 faturas com três linhas cada e 2.000 pagamentos, seis em paralelo — em 605 segundos, com zero falhas. Depois medimos a latência das páginas de um único cliente fora dos EUA, 100 requisições sequenciais por página após cinco de aquecimento, logados como o usuário demo:

Medido · 12 de outubro de 2026 · 1.024 faturas, 3.072 linhas, 2.024 pagamentos · 100 requisições por página, um cliente fora dos EUA
1,27 slista de faturas, p50, todas as linhas numa página (0.1.0)
→
632 mslista de faturas, p50, 100 linhas por página (0.1.1)
510 msThis week, p50 (p95 615 ms)
Tamanho do banco · pg_database_size, mesmo dataset
9,5 MBcom os dados de exemplo, 24 faturas
→
12,6 MBcom 1.024 faturas
≈3 KBpor fatura com linhas e dois pagamentos

As listas foram a única coisa que não sobreviveu ao dataset na forma como foram escritas: a página de faturas renderizava as 1.024 linhas de uma vez e levava 1,27 s. A versão 0.1.1 pagina a 100 linhas, e o número acima é da segunda rodada.

Armazenamento não é o limite que uma pequena empresa alcança: cerca de 3 KB por fatura com suas linhas e dois pagamentos significa que o pacote de 1 GB comporta algo na ordem de 300.000 faturas.

O limite de conexões é. A role de banco do add-on aceitou 15 conexões extras além da que o app segurava e então respondeu too many connections for role — cerca de 16 por app. O pool do template é limitado a 5; um pool dimensionado para um banco maior derrubaria este app.

O que encontramos enquanto construíamos

  • O Autodeploy fica desligado num app criado no plano Free, e fazer o upgrade não o liga. Build & deploy → Autodeploy → Save, e o diálogo oferece fazer o deploy na hora.
  • Uma mudança de plano faz o redeploy do app, e a borda respondeu bad gateway por cerca de um minuto enquanto o container novo subia. O banco reativado voltou no lugar, com seus dados.
  • Um plano cancelado bloqueia o banco em vez de apagá-lo: a página Database diz que os dados são mantidos por sete dias e voltam no próximo deploy quando o plano é reativado. Vimos isso depois de um soluço no pagamento.
  • A dashboard leu nosso .env.example: um diálogo Finish your setup listou SETUP_SECRET e o comando de start que ela tinha detectado, e pediu o valor. Um bom lugar para digitá-lo — no app em que as variáveis funcionaram.
  • Listas precisam de paginação antes de 1.000 linhas, veja acima.
  • A proteção de push do secret scanning do GitHub vem desligada num repositório público por padrão, e um fork não a herda. Nós a ligamos no template; o seu fork é seu para ligar, e a regra vale de qualquer forma: nada que seja senha ou dado de cliente vai para um arquivo.

Quando não usar isto

Use QuickBooks, Wave ou Xero quando precisar de folha de pagamento, integração bancária, declaração de impostos ou de um contador com login próprio. Use uma planilha quando emitir um punhado de faturas por ano.

Use um starter voltado a desenvolvedores — nosso controle de fluxo de caixa é um — quando quiser um backend-as-a-service com permissões por classe e Cloud functions em vez de um processo e um banco.

E note o que este post não testou: uma pessoa não técnica seguindo só este texto, do fork à restauração. Os tempos das etapas são da plataforma; a parte humana ainda não foi cronometrada. Quando rodarmos esse teste, este parágrafo será substituído pelo resultado.

O repositório

github.com/templates-back4app/firmbook — MIT. O README.md é o guia de deploy; docs/guides/ tem backup e restauração, atualizações, o que fazer quando algo quebra, personalização segura com um assistente de IA (um prompt que só pode tocar em custom/) e como anexar seu próprio domínio.

test/run-all.sh roda as 84 verificações locais — matemática das faturas, roles, replay de sessão, setup concorrente, emissão concorrente, envios duplos, backup consistente sob escritas, restauração num app novo, uma versão cuja segunda migração falha — e test/live-check.mjs, as 18 que rodamos contra o app no ar.

docs/notes/gate-1-platform.md é o diário da plataforma por trás de cada número daqui.

Tagsinvoicingsmall-businesspostgresqlcontainersbuildpacksnodeexpressno-terminalbackup

Perguntas frequentes

Preciso saber programar para montar isso?

Não, e também não precisa de terminal. Cada etapa é uma página no navegador: fazer o fork do repositório no GitHub, criar o app na dashboard da Back4app com o add-on PostgreSQL ligado, digitar uma frase secreta, abrir a URL e criar a conta do dono. Mudar como o app calcula ou armazena dados é outra história — isso exige um desenvolvedor.

Quanto custa para rodar?

Conforme listado na dashboard da Back4app em outubro de 2026: o container Starter custa US$ 5 por mês e o pacote PostgreSQL (1 GB) custa US$ 5 por mês, cobrados pro rata — US$ 10 por mês no total. O plano Free não pode ter banco de dados. Um domínio próprio, se você quiser um, é comprado à parte num registrador.

Meus dados estão seguros se a hospedagem acabar?

O app tem um botão Backup que baixa um único arquivo com tudo — contatos, faturas, pagamentos, contas, configurações, pessoas — e uma página Restore que reconstrói um deployment novo a partir desse arquivo. Cronometramos os dois no app ao vivo: um backup com 1.024 faturas levou 2,2 segundos e uma restauração, 0,7 segundo. O banco em si é PostgreSQL padrão, e há também uma exportação CSV para planilhas.

Um cliente consegue abrir o link da fatura sem ter conta?

Sim. Cada fatura emitida recebe um link de compartilhamento com um token aleatório de 32 bytes. A página mostra aquela fatura e nada mais, é marcada para não ser indexada por buscadores, não envia referrer e pode ser revogada a partir da fatura. O Google Safe Browsing não reportou nenhum problema para o hostname do nosso app; uma ferramenta de segurança em uma das nossas máquinas sinalizou todos os hosts *.b4a.run, e é por isso que o guia inclui como anexar seu próprio domínio.

O que acontece quando instalo uma atualização do template e ela quebra?

Toda migração de uma versão roda em uma única transação de banco, então uma atualização que falha não aplica nada, e o app mostra uma página simples de 'temporarily unavailable'. Na dashboard da Back4app, os Runtime logs mostram o motivo e o Instant Rollback traz a versão anterior de volta — 3 a 7 segundos nas nossas rodadas — sem tocar no banco de dados.

Isto é um software de contabilidade?

Não. Ele acompanha faturas, pagamentos, contas e o que está a vencer, em uma moeda, sem partidas dobradas, sem integração bancária e sem declaração de impostos. Se você precisa de folha de pagamento, conciliação bancária ou acesso para o contador, QuickBooks, Wave ou Xero são as ferramentas certas; este app é para o negócio que precisa de faturas e contas sob controle e quer ser dono dos dados.

Quais opções de banco de dados a Back4app oferece?

Duas, conforme o produto. Um backend da Back4app (Parse Server) guarda as classes em MongoDB e as serve por uma API REST, com permissões por classe e por linha. O Back4app Containers pode anexar um banco PostgreSQL gerenciado, que é o que este app usa: PostgreSQL 18.3, pacote de 1 GB por US$ 5 por mês, como listado em outubro de 2026. O plano Free de container não pode ter banco de dados.

Leitura complementar

Revisado e verificado por Back4app Engineering, Editorial review na Back4app

Testado em 12 de outubro de 2026 — Containers: Back4app, plano Starter, 0,5 vCPU, 512 MB, add-on PostgreSQL 1 GB (PostgreSQL 18.3) · Template: Node 22, Express 5, node-postgres 8.

Mais da Back4app Engineering

Containers · tutorial

Como criar um controle de fluxo de caixa com JavaScript

Agente de IA · tutorial

How to Turn a Spreadsheet Into a Web App With a Database and Login

Containers · error-guide

Por que seu app funciona localmente e quebra quando você faz o deploy