"Offline primeiro" é um paradigma de desenvolvimento de aplicativos no qual os desenvolvedores garantem que a funcionalidade de um aplicativo não seja afetada por uma perda temporária de conectividade de rede. Os aplicativos da Web progressivos, que parecem aplicativos nativos, mas são executados como aplicativos da Web, geralmente são construídos com base nesse paradigma.
Este tutorial ensinará como criar um aplicativo offline com Node.js e um banco de dados SQLite. Vamos começar com uma introdução aos aplicativos da Web progressivos.
Introdução ao PWA
Progressive Web Apps (PWAs) são aplicativos da Web que usam service workers, manifestos e outros recursos de plataforma da Web e aprimoramento progressivo para fornecer aos usuários uma experiência comparável aos aplicativos nativos.
Às vezes, os PWAs podem superar os aplicativos nativos em termos de eficiência. Eles operam sob demanda e estão sempre disponíveis sem consumir memória ou dados valiosos do smartphone. Os usuários consomem menos dados ao escolher um PWA em vez de uma versão nativa do mesmo aplicativo. Eles ainda podem salvar o PWA na tela inicial; é instalável sem a necessidade de um download completo.
O que estamos construindo?
Para demonstrar o poder dos aplicativos da Web progressivos, criaremos um aplicativo de blog simples.
O usuário poderá interagir com ele como outros PWAs, como o Twitter PWA. Vamos direto ao assunto.
Inicialize o aplicativo NodeJs
Vamos sujar as mãos. Para começar, vamos criar nossa pasta de projeto com o comando abaixo:
mkdir PWA && cd PWA
Em seguida, inicializaremos um aplicativo Node.js com os comandos abaixo:
npm init -y
O comando acima cria um
package.json
arquivo para o aplicativo. Em seguida, crie a seguinte estrutura de pastas em nossa pasta do projeto:
Configurar um servidor Express
Com a configuração do nosso aplicativo, vamos instalar o Express para criar nosso servidor Node.js com o comando abaixo:
npm install express
Em seguida, criaremos algumas pastas e arquivos na pasta pública:
- arquivo css/style.css
- arquivo js/app.js
Em seguida, crie um
index.js
arquivo no diretório raiz do projeto com os seguintes trechos de código abaixo:const express = require("express");
const path = require("path");
const app = express();
app.use(express.static(path.join(__dirname, "public")));
app.get("/", function (req, res) {
res.sendFile(path.join(__dirname, "public/index.html"));
});
app.listen(8000, () => console.log("Server is running on Port 8000"));
No snippet de código, importamos express para criar nosso servidor e o caminho módulo. Configuramos nosso aplicativo para renderizar nossos arquivos estáticos usando o express.static método, que leva o caminho para a pasta estática (pública), criamos a rota raiz da nossa aplicação e renderizamos o index.html Arquivo. Em seguida, configuramos o aplicativo para escutar a porta 8000 .
Conecte-se ao banco de dados SQLite
Com a configuração do servidor para nosso aplicativo, vamos criar e conectar nosso aplicativo para salvar os detalhes do nosso blog. Para começar, execute o comando abaixo para instalar a dependência sqlite3.
npm install sqlite3
Em seguida, no ponto de entrada
index.js
arquivo, adicione o trecho de código abaixo para criar e conectar o aplicativo a um banco de dados SQLite. const db = new sqlite3.Database("db.sqlite", (err) => {
if (err) {
// Cannot open database
console.error(err.message);
throw err;
} else {
console.log("Connected to the SQLite database.");
}
});
Em seguida, criaremos uma lista de blogs que armazenaremos em nosso banco de dados e renderizaremos posteriormente para o lado do cliente com o snippet de código abaixo:
let blogs = [
{
id: "1",
title: "How To Build A RESTAPI With Javascript",
avatar: "images/coffee2.jpg",
intro: "iste odio beatae voluptas dolor praesentium illo facere optio nobis magni, aspernatur quas.",
},
{
id: "2",
title: "How to Build an Offline-First Application with Node.js,"
avatar: "images/coffee2.jpg",
"iste odio beatae voluptas dolor praesentium illo facere optio nobis magni, aspernatur quas.",
},
{
id: "3",
title: "Building a Trello Clone with React DnD",
avatar: "images/coffee2.jpg",
intro: "iste odio beatae voluptas dolor praesentium illo facere optio nobis magni, aspernatur quas.",
},
];
Cada postagem de bloco em nosso aplicativo terá um id , título , avatar e introdução Campos.
Agora crie um nome de tabela de banco de dados blogs e salve os detalhes do blog que acabamos de criar acima com o snippet de código abaixo:
db.run(
`CREATE TABLE blog (id INTEGER PRIMARY KEY AUTOINCREMENT, title text,avatar text,intro text)`,
(err) => {
if (err) {
// console.log(err)
// Table already created
} else {
// Table just created, creating some rows
var insert = "INSERT INTO blogs (title, avatar, intro) VALUES (?,?,?)";
blogs.map((blog) => {
db.run(insert, [
`${blog.title}`,
`${blog.avatar}`,
`${blog.intro}`,
]);
});
}
}
);
No snippet de código, criamos uma tabela blogs usando o db.run. O db.run O método usa uma consulta SQL como parâmetro, então percorremos nosso array de blogs e os inserimos na tabela de blogs que acabamos de criar usando a função js map.
Visualizar registros do banco de dados
Agora vamos ver os registros que acabamos de criar usando Arctype. Para visualizar os registros em seu banco de dados SQLite usando Arctype, siga os passos abaixo:
- Instalar Arctype
- Execute o aplicativo com
node index.js
para criar um banco de dados - Inicie o Arctype e clique na guia SQLite
- Clique em Selecionar arquivo SQLite botão e localize o db.sqlite arquivo gerado quando você executou o servidor.
- Você deve ver a tabela de blogs e os registros que criamos, conforme mostrado na captura de tela abaixo:
Renderize a página
Neste ponto, conectamos o aplicativo a um banco de dados SQLite e também inserimos alguns registros no banco de dados. Agora, abra o index.html e adicione os seguintes trechos de código abaixo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="stylesheet" href="css/style.css" />
<title>Blogger</title>
<link rel="manifest" href="manifest" />
</head>
<body>
<section>
<nav>
<h1>Blogger</h1>
<ul>
<li>Home</li>
<li class="active">Blog</li>
</ul>
</nav>
<div class="container"></div>
</section>
<script src="js/app.js"></script>
</body>
</html>
Criamos uma marcação simples com links para nosso manifesto no arquivo acima, que criaremos na próxima seção, estilos e app.js arquivos.
Em seguida, criaremos um blogs rota em nosso index.js arquivo para retornar os blogs para o lado do cliente.
...
app.get("/blogs", (req, res) => {
res.status(200).json({
blogs,
});
});
...
Em nosso public/js/app.js , enviaremos uma solicitação get ao endpoint do blog para obter os blogs do nosso back-end. Em seguida, percorremos os blogs, segmentamos o contêiner classe e exibi-los.
let result = "";
fetch("http://localhost:8000/blogs")
.then((res) => res.json())
.then(({ rows } = data) => {
rows.forEach(({ title, avatar, intro } = rows) => {
result += `
<div class="card">
<img class="card-avatar" src="/${avatar}"/>
<h1 class="card-title">${title}</h1>
<p class="intro">${intro}</p>
<a class="card-link" href="#">Read</a>
</div>
`;
});
document.querySelector(".container").innerHTML = result;
})
.catch((e) => {
console.log(e);
});
Também adicionaremos alguns estilos ao nosso aplicativo no public/css/style.css com o trecho de código abaixo:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #fdfdfd;
font-size: 1rem;
}
section {
max-width: 900px;
margin: auto;
padding: 0.5rem;
text-align: center;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
}
ul {
list-style: none;
display: flex;
}
li {
margin-right: 1rem;
}
h1 {
color: #0e9c95;
margin-bottom: 0.5rem;
}
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
grid-gap: 1rem;
justify-content: center;
align-items: center;
margin: auto;
padding: 1rem 0;
}
.card {
display: flex;
align-items: center;
flex-direction: column;
width: 15rem auto;
background: #fff;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
border-radius: 10px;
margin: auto;
overflow: hidden;
}
.card-avatar {
width: 100%;
height: 10rem;
object-fit: cover;
}
.card-title {
color: #222;
font-weight: 700;
text-transform: capitalize;
font-size: 1.1rem;
margin-top: 0.5rem;
}
.card-link {
text-decoration: none;
background: #16a0d6e7;
color: #fff;
padding: 0.3rem 1rem;
border-radius: 20px;
margin: 10px;
}
.intro {
color: #c2c5c5;
padding: 10px;
}
.active {
color: #16a0d6e7;
}
Agora abra o package.json arquivo e adicione o script de início.
"start": "node index.js"
Neste ponto, configuramos nosso aplicativo. Mas não podemos executar nosso aplicativo quando o servidor não está em execução ou quando não há conexão de rede para produção. Vamos configurar isso na próxima seção.
Otimizando o aplicativo
Precisamos tornar nosso aplicativo compatível com todos os tamanhos de tela. Também adicionaremos uma cor de tema adicionando a marcação abaixo na seção head do nosso index.html Arquivo.
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#16a0d6e7"/>
Criar um manifesto
Precisamos descrever nosso aplicativo e como ele deve se comportar quando instalado no dispositivo do usuário. Podemos fazer isso criando um manifesto.
Crie um manifesto arquivo no diretório raiz do projeto e adicione as seguintes configurações:
{
"name": "Blogger"
"short_name": "Blogger"
"start_url": "/",
"display": "standalone",
"background_color": "#0e9c95",
"theme_color": "#16a0d6e7",
"orientation": "portrait",
"icons": []
}
Em nosso manifesto, definimos as seguintes configurações:
- nome :define o nome de exibição do aplicativo.
- nome_curto :define o nome que será exibido sob o ícone do aplicativo quando instalado.
- start_url :informa ao navegador a URL raiz do aplicativo.
- exibir :informa ao navegador como exibir o aplicativo.
- cor_de fundo: Isso define a cor de fundo do aplicativo quando instalado.
- theme_color: Isso define a cor da barra de status.
- orientação: Isso define a orientação a ser usada durante a exibição do aplicativo.
- ícones: Isso define os ícones ou imagens de tamanhos diferentes a serem usados como ícones da página inicial do nosso aplicativo.
Criar nossos ícones da tela inicial manualmente pode ser uma tarefa muito complicada, mas não se preocupe. Aproveitaremos um módulo de terceiros conhecido como pwa-asset-generator para gerar ícones de diferentes tamanhos a partir do ícone do nosso aplicativo principal dentro do diretório público com o comando abaixo:
#change directory to the public folder
cd public
#generate icons
npx pwa-asset-generator logo.png icons
O comando acima criará um ícones pasta dentro da pasta pública com muitos ícones para nosso aplicativo, juntamente com alguns JSON no terminal que vamos colar em nosso array de ícones no manifesto.
A matriz de ícones em nosso manifesto deve ficar assim:
"icons": [
{
"src": "public/icons/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "public/icons/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "public/icons/manifest-icon-512.maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "public/icons/manifest-icon-512.maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
Além disso, o comando gerou os links de marcação para os ícones gerados.
Copie e cole a marcação na seção head da marcação no public/index.html Arquivo.
Configure Service Workers
Com nosso manifesto criado, vamos configurar os service workers. Um service worker é um pedaço de código JavaScript que seu navegador executa em segundo plano em um encadeamento separado para manipular o cache de ativos e dados que você salva para solicitações futuras para ativar o suporte offline para seu aplicativo.
Portanto, crie um blogger.serviceWorker.js arquivo no público pasta. Para o service worker, há muitos eventos (push, activate, install, fetch, message, sync), mas para a demonstração neste tutorial, abordaremos o instalar, ativar e buscar eventos. Antes disso, precisamos criar um array para armazenar todos os ativos que usamos em nossa aplicação.
const assets = [
"/",
"css/style.css",
"js/app.js",
"/images/blog1.jpg",
"/images/blog2.jpg",
"/images/blog3.jpg,"
];
Em seguida, ouviremos a instalação evento para registrar e salvar nossos arquivos estáticos no cache do navegador. Esse processo leva algum tempo para ser concluído. Para pular a espera, usaremos skipWaiting().
const BLOGGER_ASSETS = "blogger-assets";
self.addEventListener("install", (installEvt) => {
installEvt.waitUntil(
caches
.open(BLOGGER_ASSETS)
.then((cache) => {
cache.addAll(assets);
})
.then(self.skipWaiting())
.catch((e) => {
console.log(e);
})
);
});
...
Em seguida, precisamos limpar o cache para remover os ativos antigos sempre que o service worker for atualizado. Para isso, ouviremos o ativar trecho de código abaixo:
...
self.addEventListener("activate", function (evt) {
evt.waitUntil(
caches
.keys()
.then((keysList) => {
return Promise.all(
keysList.map((key) => {
if (key === BLOGGER_ASSETS) {
console.log(`Removed old cache from ${key}`);
return caches.delete(key);
}
})
);
})
.then(() => self.clients.claim())
);
});
No snippet de código acima, usamos o waitUntil método no service worker. Esse método aguarda a conclusão da ação e, em seguida, verificamos se os ativos que estamos tentando limpar são os ativos do nosso aplicativo atual antes de excluí-los.
Em seguida, precisamos dos arquivos armazenados em nosso cache para usá-los.
self.addEventListener("fetch", function (evt) {
evt.respondWith(
fetch(evt.request).catch(() => {
return caches.open(BLOGGER_ASSETS).then((cache) => {
return cache.match(evt.request);
});
})
);
})
Quando uma solicitação é feita na página, o PWA verificará nosso cache e lerá se houver dados no cache em vez de ir para a rede. Em seguida, usando o responderCom , sobrescrevemos o padrão do navegador e fazemos nosso evento retornar uma promessa. Quando o cache estiver completo, podemos retornar o cache correspondente ao evt.request. Quando o cache estiver pronto, podemos retornar o cache que corresponde ao evt.request.
Configuramos com sucesso nosso service worker. Agora vamos disponibilizá-lo em nosso aplicativo.
Registrar o Service Worker
Agora vamos registrar nosso service worker em nosso public/js/app.js arquivo com o trecho de código abaixo:
...
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker
.register("/blogger.serviceWorker.js")
.then((res) => console.log("service worker registered"))
.catch((err) => console.log("service worker not registered", err));
});
}
Aqui, verificamos se o navegador do nosso aplicativo oferece suporte a service workers (é claro, nem todos os navegadores oferecem suporte a service workers) e, em seguida, registramos nosso arquivo de service worker.
Agora execute a aplicação com o comando abaixo:
npm start
Vá para localhost:8000 em seu navegador para acessar o aplicativo.
Google Lighthouse Check
Agora vamos verificar se configuramos corretamente nosso PWA usando uma verificação do Google Lighthouse. Clique com o botão direito do mouse em seu navegador e selecione "inspecionar". Nas guias de inspeção, selecione farol e clique em gerar relatório. Se tudo correu bem com seu aplicativo, você deverá ver uma saída como a da captura de tela abaixo:
Criamos com sucesso nosso primeiro aplicativo. Você também pode parar o servidor para testar o aplicativo no modo offline.
Conclusão
Os Progressive Web Apps (PWA) usam APIs modernas para fornecer recursos, confiabilidade e instalabilidade aprimorados com uma única base de código. Eles permitem que seu usuário final use seu aplicativo independentemente de ter ou não uma conexão com a Internet. Você deve se sentir à vontade para bifurcar o repositório e adicionar recursos adicionais ao projeto. Boa sorte!