Repositório de código utilizado na disciplina de Backend II
  • JavaScript 39.3%
  • EJS 34.4%
  • CSS 25.7%
  • Dockerfile 0.6%
Find a file
2023-06-06 16:11:24 -04:00
models Adicionado Deployment 2023-04-10 20:40:14 -04:00
public Modificado banner 2022-09-27 13:20:00 -04:00
views Adicionado Deployment 2023-04-10 20:40:14 -04:00
.dockerignore Corrigido detalhes para ser mais fácil executar Dockerfile 2023-05-30 16:47:23 -04:00
.env.example Corrigido detalhes para ser mais fácil executar Dockerfile 2023-05-30 16:47:23 -04:00
.gitignore Adicionado gitignore 2022-09-23 15:41:36 -03:00
deploy.yaml Corrigido detalhes para ser mais fácil executar Dockerfile 2023-05-30 16:47:23 -04:00
docker-compose.yml Adicionado docker compose 2023-06-06 16:11:24 -04:00
Dockerfile Corrigido detalhes para ser mais fácil executar Dockerfile 2023-05-30 16:47:23 -04:00
package-lock.json Adicionado Deployment 2023-04-10 20:40:14 -04:00
package.json Adicionado Deployment 2023-04-10 20:40:14 -04:00
README.md Adicionado banco MongoDB 2022-09-23 17:57:07 -03:00
server.js Corrigido detalhes para ser mais fácil executar Dockerfile 2023-05-30 16:47:23 -04:00

NodeJS-Express-EJS

Repositório de código utilizado na disciplina de Backend II

Preparando Ambiente.

Antes de começarmos, é necessário preparar o ambiente instalando o NodeJS e alguns módulos que iremos utilizar.

Instalação do Nodejs

Windows: instalador

No windows, basta realizar o download do instalador na página abaixo: "https://nodejs.org/en/download/"

Linux: nvm

Em distribuições Linux, é mais prático instalar o Node com o gerenciador de versões NVM, desta forma você poderá facilmente mudar a versão ativa e instalar versões diferentes para cada usuário. Basta seguir as instruções na página oficial do NVM no github: https://github.com/nvm-sh/nvm

    # Instalar nvm  * Reiniciar terminal depois
    wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

    # Instala a última versão lts do node
    nvm install --lts

Preparando o ambiente

Para que possamos desenvolver aplicações com Express e EJS, é necessário iniciar um ambiente e instalar alguns módulos.

em um novo diretório de sua preferência, com o NodeJS já instalado, execute o comando abaixo para iniciar um projeto com um arquivo package.json padrão.

	# Inicializando o package.json
	npm init -y

Express

Instale o módulo express que segundo o próprio site oficial:

O Express é um framework para aplicativo da web do Node.js mínimo e flexível que fornece um conjunto robusto de recursos para aplicativos web e móvel.

	# Instalar o Express
	npm i express

EJS

Estaremos utilizando também o EJS, ou 'Embedded JavaScript templating'. Permite que o código html seja entreposto com Javascript, funcionando como um template da página que será preenchido com informações do servidor. (PHP virou JS?)

    # Instalando EJS
	npm i ejs
	
	# Layout
	npm install ejs express express-ejs-layouts

Nodemon

Instale O módulo Nodemon, que permite que a aplicação automaticamente seja reiniciada a cada salvamento dos arquivos, de forma que permitirá rápida iteração e testes. (A opção --save-dev indica que esse módulo será necessário apenas como dependência dev, ou seja, que não será necessário na build final de produção)

	# Instalar Nodemon, que facilita reiniciar o servidor
	npm i --save-dev nodemon

Para que seja possível iniciar o servidor pelo nodemon, adicione a seguinte configuração no arquivo package.json

Configurar o devStart para iniciar o arquivo do servidor js

    "scripts": {
     "devStart": "nodemon server.js"
    }

Então, inicie o servidor para verificar se está funcionando com o comando abaixo:

	# comando para iniciar o servidor
	npm run devStart

(Dará erro 'module not found' porque você não criou o arquivo server.js, mas isso é normal, continue abaixo)

Pronto! você tem tudo pronto para começar a fazer um site com Node.

Página Mínima Express

Ainda não tem nada, nenhuma tela, nenhum site. Então agora crie um arquivo server.js, e o inicie com o conteúdo necessário para que o servidor escute na porta 5000 e escreva algo na tela quando acessar pelo navegador.

./server.js

	const express = require('express')
	const app = express()

    // Deixe por último
	app.listen(5000)

Acesse http://localhost:5000 e veja o que aparece:

Cannot GET /

(Eu disse que iria escrever 'algo' na tela)

Rotas

O erro acima basicamente está nos informando que a rota / não existe, então basta utilizarmos o express para definir a primeira rota.

Uma rota é uma associação entre um método HTTP, uma URL e um método de um controlador, responsável por processar a requisição e gerar a resposta dessa requisição.

É possível criar rotas com qualquer um dos métodos HTTP (GET, POST, PUT, PATCH, DELETE) e com qualquer URL que preferir. Vamos definir uma rota GET no URL inicial da página, /

	app.get("/", (req, res) => {
		console.log("Executou");
		res.send("<h1>FUNCIONOU</h1>")
	})

Agora, quando acessado a página exibe FUNCIONOU

A função express.get irá esperar um parâmetro com a url pretendida e um callback que responderá quando a rota for acessada, este callback pode receber vários parâmetros, porém por enquanto podemos apenas utilizar os dois primeiros: req, res.

req

Este parâmetro contém as informações da requisição, como por exemplo o url que foi solicitado, os parêmetros da query no URL, o corpo do POST, entre outras informações da requisição.

res

Este parâmetro é o que iremos chamar métodos para produzir algum conteúdo, é a resposta. É possível por exemplo enviar HTML puro como texto, um Arquivo como uma imagem ou outro tipo de documento, uma página que irá renderizar HTML, ou apenas enviar algum número de status de erro.

	res.send("<h1>TESTE</h1>") // Envia HTML
	
	res.sendStatus(404) // Envia um HTML Status Code
	
	res.status(500).json({message: "Error"}) // Envia um erro 500 e a mensagem de erro em json
	
	res.download("imagem.png") // Solicita ao navegador para fazer download do arquivo
	
	res.render("pages/sobre") // renderiza outro arquivo. 

View Engine

Para que um arquivo seja renderizado, é necessário especificar qual o View Engine que será utilizado, isto é, como o servidor deve interpretar os arquivos que irão produzir HTML, exatamente, que irão produzir HTML.

Por Utilizarmos NodeJS com express e EJS, iremos estar produzindo um site SSR (Server-Side-Rendering), ou Renderização do Lado do Servidor. O SSR inverte o processo de renderização, onde o próprio servidor produz o HTML. O SSR pode fornecer aos usuários um carregamento mais eficiente da aplicação, já que parte da renderização é feita no servidor.

Uma das vantagens de aplicações assim é que motores de busca terão facilidade em indexar a página, já que parte da renderização é feita no servidor, a maior parte das informações do site já está no HTML recebido da resposta, sem a necessidade de executar Javascript no lado cliente.

Outra questão é que quando comparado a SPA (Single Page Apps), uma página SSR garante uma melhor performance, já que foi o servidor quem trabalhou em gerar a maior parte do HTML, o trabalho do Navegador é apenas exibir as informações na tela, e não executar dezenas de minhares de linhas de código Javascript (Dependendo da velocidade de processamento do cliente acessar um SPA pode produzir uma página em branco inicialmente, até que o JS seja carregado, problema inexistente no SSR).

Quais as vantagens de utilizar SSR?

  • Ser indexado pelo Google ou outros mecanismos de pesquisa é essencial
  • É necessário que o site funcione em dispositivos legados (Com pouco ou nenhum suporte a JS)
  • Quando a aplicação é muito grande e possui muitas páginas.

Obs: Páginas SSR sofrem de problemas de carregamento a cada clique de link e/ou redirecionamentos, portanto tenha em mente que para aplicações dinâmicas e interativas SPA é uma melhor escolha

EJS

Iremos estar utilizando o View Engine EJS, que é uma forma de produzir uma página HTML com partes Javascript que escrevem a página.

Para definir o View Engine no Express, basta configurá-lo utilizando a função app.use. Também coloque na rota get '/' para que seja renderizado a página index

./server.js

    ...
    app.set('view engine', 'ejs')

	app.get("/", (req, res) => {
		console.log("Executou");
		res.render("index");
	})
    ...

Em seguida, vamos definir um arquivo que será a página inicial do site, em ./views/index.ejs

./views/index.ejs

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="pragma" content="no-cache">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Página Inicial</title>
</head>
<body>
    <h1>Início</h1>

    <p>Esta página é a primeira página do site.</p>
</body>
</html>

Obs: Se você está utilizando o VS-Code, instale o plugin que destaca o código EJS sem problemas: "EJS language support"

EJS - Utilizando Javascript como linguagem de Template

É possível, na página index.ejs, produzir código HTML que será exibido no navegador utilizando trechos de código Javascript.

Veja só como funciona:

...
<body>
    <h1>Início</h1>

    <p>2 + 2 é igual a <%= 2 + 2 %></p>
</body>
...

A tag especial <%= %> delimita onde o código javascript irá produzir uma saída para ser exibida na página. No caso a página acima irá exibir '2 + 2 é igual a 4', não porque javascript foi executado no navegador, mas sim porque no lado servidor a página foi gerada assim.

Abra as ferramentas de desenvolvedor no seu navegador e verifique a resposta raw HTML que o servidor enviou, lá estava <p> 2 + 2 é igual a 4</p>, o navegador do cliente nunca viu o código javascript, ele apenas recebe html. Para o navegador do cliente, é mesma coisa como se essa página fosse estáticamente servida.

Tags EJS

https://ejs.co/

  • <% 'Scriptlet' tag, permite javascript de controla o que será exibido mas não produz saída
  • <%_ ‘Whitespace Slurping’ Scriptlet tag, Remove espaços em branco anteriores
  • <%= Envia o valor de retorno ao documento (com HTML e símbolos escapados, ex: &lt;)
  • <%- Envia o valor de retorno ao documento, as is, ou seja, funciona HTML.
  • <%# Comentário
  • <%% Para escrever as letras <% na página (Sim, escrever os dois caracteres no documento)
  • %> Fechamento de tag simples
  • -%> Trim-mode ('newline slurp') tag, impede que contenha quebra de linha
  • _%> ‘Whitespace Slurping’ ending tag, remove todo espaço após

Obs: Veja que diferente do PHP, que a saída de dados produzido pelo echo() é enviado ao documento, no EJS, é o valor de retorno da expressão que é enviado ao documento. Parecido com como o react por exemplo lida com o JSX

Obs²: Caso ainda assim, queira produzir conteúdo chamando uma função, é possível chamar a função __append(), que é como o EJS funciona por trás dos panos

Enviando informações para a página EJS

É possível enviar dados para a página, de forma que poderíamos realizar alguma pesquisa no banco de dados baseado na requisição e então enviar esses dados para a página renderizar, com ajuda do EJS.

Veja como configurar o título e o conteúdo que deve aparecer na página, enviando ele para o template. Primeiro precisamos passar os valores como um objeto na função res.render()

./server.js

    ...
	app.get("/", (req, res) => {
		console.log("Executou");
		res.render("index",{ 
            titulo:"Seja bem vindo!", 
            conteudo:"Obrigado por acessar esta página" 
        });
	})
    ...

E então no arquivo index.ejs inserimos os valores passados:

./views/index.ejs

<body>
    <h1><%= titulo %></h1>

    <p><%= conteudo %></p>
</body>

Obs: caso queira, é possível acessar estes valores pela variável locals ( locals.titulo e locals.conteudo por exemplo), inclusive é a forma correta para condicionalmente verificar a existência de uma propriedade do objeto sem produzir um erro if(locals.conteudo)

	<p>Insire no HTML o valor passado, ou 'Default' se ele não existir</p>
	<b>Valor:</b><i><%= locals.text || "Default" %></i>

As tags EJS podem conter qualquer código Javascript, não só os valores de propriedades, inclusive utilizando as tags <% %> é possível executar qualquer statement, incluindo laços de repetição, definição de funções, etc...

Apenas para demonstrar o uso do EJS com a tag <%, veja o exemplo abaixo que recebendo um objeto {iteracoes:10} escreve a sequência de fibonacci com tags <h2>

<body>
    <h1>Sequência de Fibonacci:</h1>
    <%
    let atual = 0
    let anterior = 1
    for(let i =0; i< iteracoes; i++)
    {
        let proximo = atual + anterior
        anterior = atual
        atual = proximo
    %>
        <h2>
        <%= atual %>
        </h2>
    <%
    }
    %>

</body>

É importante notar que as tags <% %> não produzem nenhuma saída, mas permitem todo tipo de controle de fluxo e statements javascript. Já as tags <%= > e <%- > produzem saída mas esperam apenas uma expressão.

Incluindo outras páginas

Muitas vezes, ao desenvolver uma página web, é necessário definir um layout padrão que será compartilhado em todas as páginas. Por exemplo, todas as páginas compartilham de uma mesma barra topo para navegação e rodapé com informações de contato ou um mapa do site.

Para fazer isso com EJS é fácil, basta definir estes 'pedaços' .ejs que por padrão são organizados em um diretório chamado 'partials' dentro de 'views'.

O objetivo aqui é apenas demonstrar como utilizar o EJS para incluir arquivos e definir páginas. O HTML e CSS foge do escopo. Neste artigo não será feito nenhum esforço para que a página fique apresentável e profissional, apenas serão demonstradas as ferramentas backend.

./views/partials/head.ejs

	<meta http-equiv="pragma" content="no-cache">
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title><%= locals.title || "Página EJS" %></title>

	<link rel="stylesheet" href="/estilos.css">

./views/partials/header.ejs

<div class="topnav">
    <div class="regionMain"><div class="main">
    <a class="topnav-link" href="/">Início</a>
    <a class="topnav-link" href="/artigo">Artigos</a>
</div></div>
</div>

./views/partials/footer.ejs

<div class="footer-container">
    <p>
        Mais Informações: <a href="https://kcire.fslab.dev/">https://kcire.fslab.dev/</a>
    </p>
</div>

./views/index.ejs

<!DOCTYPE html>
<html>
<head>
    <%- include('./partials/head'); %>
</head>
<body>
    <header class="header">
        <%- include('./partials/header'); %>
    </header>

    <h1>Início</h1>

    <p>Esta página é a primeira página do site.</p>

    <footer class="footer">
        <%- include('./partials/footer'); %>
    </footer>

</body>
</html>

Desta forma, a página index inclui outras partes que estão em outros arquivos, por meio da tag <%- %> que produz HTML não escapado. (Lembre-se que <%= %> produziria na página apenas texto, escrevendo o HTML escapado e não enviando como tags normais)

express-ejs-layouts

O módulo express-ejs-layouts simplifica o processo de utilizar estes layouts, por configurar um arquivo de layout padrão ex: layout.ejs que receberá o conteúdo da página como um valor que será escrito. (Sim primeiro a página é renderizada, e então o resultado de sua renderização é enviado ao layout como texto)

Configurando ejs-layout

https://raddy.dev/blog/nodejs-express-layouts-and-partials/

./server.ejs

	const expressLayouts = require('express-ejs-layouts');

    ...
    
    app.use(expressLayouts)
    app.set("layout","./layouts/default");

Criar arquivo de layout em ./views/layouts/default.ejs

<!DOCTYPE html>
<html>
<head>
    <%- include('../partials/head'); %>
</head>
<body>
    <header class="header">
        <%- include('../partials/header'); %>
    </header>

    <div class="regionMain">
    <main class="main">
        <%- locals.body || "Nenhum conteúdo" %>
    </main>
    </div>

    <footer class="footer">
        <%- include('../partials/footer'); %>
    </footer>

</body>
</html>

obs: Este arquivo inclui o corpo das páginas com locals.body

E então a página index só precisa do seguinte conteúdo:

./views/index.ejs

<div class="invert-section banner">
    <h2 style="margin: auto;text-align: center;">NODE EXPRESS EJS</h2>
</div>

<div class="regionContent">
    <section class="section">
        <h2 class="section-title">Início</h2>
        <p>Esta página é a primeira página do site.</p>
    </section>
</div>

Utilizar um sistema de layout facilita a programação do site e mantém a regra DRY - Não Repita Você Mesmo, garantindo que mesmo que dezenas de páginas utilizem o mesmo layout, não haverá código duplicado

Escolhendo o layout

É possível configurar o layout a ser utilizado em todas as páginas, ou mesmo uma única rota. Veja:

	// Controlar qual layout usar em todas as páginas
	app.set('layout','./layouts/full-width')
	
	// Controlar qual layout usar com as informações passadas a uma rota específica:
	res.render('about', { layout: './layouts/sidebar' })

Servindo página estáticas

Para que o site tenha estilos css, imagens, entre outros documentos, é necessário configurar para que o servidor ofereça estes arquivos de forma estática, isto é, sem interpretação alguma de código, apenas enviando o conteúdo.

./server.js


// Oferecer arquivos estáticos em './public/'
app.use(express.static('public'));

Caso no diretório ./public/ tenha um arquivo de estilos, por exemplo: ./public/estilos.css este arquivo estará disponível em http://localhost/estilos.css

Veja que 'public' não é incluído no url, para que você sirva conteúdo estático apenas em um subdiretório, especifique-o da seguinte forma: app.use('/css', express.static(__dirname + 'public/css')), onde apenas urls com /css irão acessar o conteúdo de ./public/css/

Agora você pode definir arquivos de estilos, imagens e javascript que preferir, dentro da pasta public e então referenciá-los no arquivo de layout correspondente.

Express Router

Vamos adicionar uma página que irá conter artigos de notícias, porém para fazer isso, é necessário definir uma rota para esta página.

O Router permite que diferentes rotas sejam redirecionadas para arquivos

Conter todas as rotas no arquivo ./server.js ficaria muito desorganizado, Cada router deve ser criado no seu próprio diretório routes, e então ser chamado no server.js:

./routes/artigos.js

    const express = require('express')
	const router = express.Router()

	router.get("/", (req, res) => {
		res.send("<h1>PÁGINA ARTIGOS</h1")
	})

    // exportar o router para ser possível utilizá-lo no server.js
	module.exports = router

./server.js

	const artigoRouter = require("./routes/artigos")

    ...

	app.use("/artigo", artigoRouter)

Caso agora acessar http://localhost:5000/artigos/, irá abrir a página artigos.

Veja que no server.js por chamar o router com app.use("/artigo"), todas as páginas em artigos.js são relativas a este url, onde o router.get("/" ... é a rota que responde a /artigo/

Uma rota router.get("/new" ... seria então responsável por responder ao url /artigo/new

É possível definir rotas que podem ter qualquer valor, permitindo assim passar valores pelo próprio caminho da página.

./routes/artigos.js

    router.route("/:id")
        .get((req,res) => {
            const artigoId = req.params.id;
            res.send("GET artigo com id "+artigoId);
        })

Inclusive, é possível anexar vários métodos HTTP um depois do outro quando utilizando o route:

    router.route("/:id")
        .get((req,res) => {
            const artigoId = req.params.id;
            res.send("GET artigo com id "+artigoId);
        })
        .post((req,res) => {
            const artigoId = req.params.id;
            res.send("POST artigo com id "+artigoId);
        })
        .delete((req,res) => {
            const artigoId = req.params.id;
            res.send("DELETE artigo com id "+artigoId);
        });

Obs: O layout não está sendo utilizado porque o res.send() envia texto plano, para que layouts sejam utilizados é necessário chamar uma página com res.render()

O que o :id representa é um parâmetro de url, ele representa qualquer caminho, /artigo/234234, /artigo/45646, /artigo/567567, /artigo/46345, /artigo/batata Todos esses caminhos iriam ser roteados de tal forma que o valor após a segunda barra '/' é o valor do id. Experimente e veja que o valor do id pode inclusive ser um texto

Middleware

https://expressjs.com/pt-br/guide/using-middleware.html
Funções de Middleware são funções que tem acesso ao objeto de solicitação (req), o objeto de resposta (res), e a próxima função de middleware no ciclo solicitação-resposta do aplicativo. A próxima função middleware é comumente denotada por uma função chamada next().

Funções de middleware podem executar as seguintes tarefas:

  • Executar qualquer código.
  • Fazer mudanças nos objetos de solicitação e resposta.
  • Encerrar o ciclo de solicitação-resposta.
  • Chamar a próxima função de middleware na pilha.

Veja que no router acima, os três métodos HTTP, get, post e delete, acessam o id do artigo, porém digamos que você queira realizar algum processamento comum às três rotas, é possível fazer isso com um Middleware, uma função que executará antes de entrar nas rotas, podendo incusive retornar um erro antes de ir para a página, servindo como uma etapa anterior de checagem.

Suponha que você tenha um array de artigos, e quer passar o objeto contido em um dos elementos para os métodos GET, POST e DELETE do router, é fácil veja só:

./routes/artigos.js

    const artigo_unknown = {titulo:"Desconhecido",conteudo:"Artigo não encontrado"}
    // FAKE DATA
    const artigos = {
    1:{titulo:"Artigo 1",conteudo:"Conteúdo do primeiro artigo"},
    2:{titulo:"Artigo 2",conteudo:"Conteúdo do segundo artigo"},

    ...

    }

    ...

    // por último
    router.param("id", (req,res,next,id) => {
        req.artigo = artigos[id]
        if(!req.artigo) req.artigo = artigo_unknown
        next()
    })

Veja que o router.param faz uma checagem se o artigo existes e então prossegue chamando a função next(), se essa função não fosse chamada o resto das rotas não seriam executadas, útil para quando se deseja produzir uma mensagem de erro antes mesmo de entrar na rota.

E então em cada rota, é fácil lidar com os valores, pois podem acessar o objeto em req.artigo

    router.route("/:id")
        .get((req,res) => {
            res.send("GET artigo "+JSON.stringify(req.artigo));
        })
        .post((req,res) => {
            res.send("POST artigo "+JSON.stringify(req.artigo));
        })
        .delete((req,res) => {
            res.send("DELETE artigo "+JSON.stringify(req.artigo));
        });

O uso de middleware não se limita roteamento com parâmetros, você pode definir uma função que será executada antes de todas as rotas, independente do url acessado, veja só:

obs: foi adicionado o app.use() antes das outras rotas, para que execute antes de todas.

./server.js


    // Realizar um log de todas as páginas que forem acessadas:
    // originalUrl contém o caminho da requisição HTTP
	function logger(req,res,next) {
		console.log(req.originalUrl);
		next();
	}
	app.use(logger);

E o comportamento ao contrário também é possível, caso deseje executar uma função após todas as páginas, basta inserir o app.use() desta função ao final do arquivo e lembrar de chamar next() nas rotas.

Um middleware pode ser utilizado também em situações onde é necessário autenticar para ter acesso a certa página, então caso o usuário não seja autenticado é redirecionado para uma página de loginm, e caso tenha sucesso chama a função next()

Por último, é possível também passar valores para uma função por inserir propriedades no objeto req, que é compartilhado por todas as funções que respondem uma determinada requisição:

	// Processamento antes de index
	function generateName(req,res,next) {
		req.name = "Erick"
		next();
	}

    // index
	app.get("/", generateName, (req, res) => {
		res.render("index", {nome: req.name});
	});

Obs: o next() não retorna a função, você pode chamar o next() no início de uma rota e então retornar o conteúdo da página.

Mais informações https://blog.webdevsimplified.com/2019-12/express-middleware-in-depth/

Tratando dados de formulários via POST

Antes de começar a realizar páginas que lidam com formulários, é necessário configurar o express para que seja capaz de decodificar os valores url-enconded enviados pelo navegador ao submeter um <form>

./server.js

// Permite receber valores do POST como urlencoded e json
app.use(express.urlencoded({extended: true}));
app.use(express.json());

Então agora caso possua um formulário, por exemplo considere o formulário abaixo que cria um novo artigo:

./views/novoartigo.ejs

<div class="regionContent">
    <section class="section">
        
        <a href="/artigo/">Voltar</a>
        <h2 class="section-title">Novo Artigo:</h2>

		<form action="" method="POST">
			<label for="titulo">Título:</label><br>
			<input type="text" id="titulo" name="titulo"><br>
			<label for="conteudo">Conteúdo:</label><br>
			<textarea id="conteudo" name="conteudo"></textarea>
			<input type="submit" value="Enviar">
		</form>
    </section>

</div>

A rota POST poderá então receber os valores dos inputs pelo parâmetro req.body

./routes/artigos.js

router.get("/new", (req, res) => {
    res.render("novoartigo")
})

router.post("/new", (req, res) => {
    const novoArtigo = {
        titulo: req.body.titulo,
        conteudo: req.body.conteudo
    };
    artigos[artigosIdCount++] = novoArtigo;

    res.redirect("/artigo/"+(artigosIdCount-1));
});

Listando Valores

Adianta nada adicionar valores aos títulos e conteudo de artigos se não podemos ler o que foi inserido. Agora, para ver os artigos criados, basta enviá-los para a página artigo.ejs, e utilizando repetição foreach renderizá-los na tela:

./routes/artigos.js

router.get("/", (req, res) => {    
    res.render("artigos", {artigos:artigos})
})

./views/artigos.ejs

<div class="regionContent">
    <section class="section">
        <h2 class="section-title">Artigos:</h2>
		<a href="/artigo/new">Novo Artigo</a>
<div>
    <%
        const artigos = Object.keys(locals.artigos);
        artigos.forEach((k) => {
            const artigo = locals.artigos[k];
    %>
        <div>
            <p>
                <a href="/artigo/<%= k %>">Detalhes</a>
                <b>Título: </b><%= artigo.titulo %>
            </p>
        </div>
    <%
        });
    %>
</div>
</section>
</div>

Lembre-se que para controle de fluxo, é necessário utilizar a tag <% %>, mas para imprimir valores como texto é <%= %>

Ainda, seria possível inserir um único 'card' de artigo como um componente e incluir o mesmo utilizando <%- include('') %>, desacoplando o código e deixando mais simples

MONGODB

MongoDB Crash Course https://www.youtube.com/watch?v=ofme2o29ngU MongoDB Cheat Sheet https://webdevsimplified.com/mongodb-cheat-sheet.html

Instalação do MongoDB

mongodb --> https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-windows/

mongodb-shell --> https://www.mongodb.com/docs/mongodb-shell/

	/*
	----------------------------
	SQL			NOSQL
	RELATIONAL	NON-RELATIONAL
	TABLE		COLLECTION
	ROW			DOCUMENT
	COLUMN		FIELD
	*/

Como utilizar o mongosh

Em um terminal execute:

	# entrando no mongodb-shell
	mongosh

Uma vez dentro do programa do mongosh, é possível utilizar comandos para criar coleções e lidar com o banco de dados

	// Limpar o terminal
	cls
	
	// Exibe as databases
	show dbs
	
	// Entrar em uma database (Mesmo que não exista)
	use <nome-do-banco>
		use appdb

	// Exibir a database atual
	db
	
	// Criar uma entrada em uma coleção
	db.<nome-da-coleção>.insertOne(<json-object>)
		db.users.insertOne({ name:"John", age:20 })
	
	// Criar uma entrada em uma coleção
	db.<nome-da-coleção>.insertMany(<json-array>)
		db.users.insertMany([{ name:"Peter", age:33 },{ name:"Mary", age:18 }])
		
	// Exibir entradas de uma coleção
	db.<nome-da-coleção>.find()
		db.users.find()
		
	// Exibir UMA entrada de uma coleção
	db.<nome-da-coleção>.findOne()
		
	// Contar entradas de uma coleção
	db.<nome-da-coleção>.countDocuments()
		
	// É possível executar funções de filtragem no find:
	.find()
		.limit(<int>) // Limita a quantidade
		.sort(<json-object>) // organiza pela chave ou chaves escolhidas. 1 é crescente, -1 decrescente.
		.skip(<int>) // Pula entradas
		
	// Procurando por valor exato
	.find({name:"Peter"}) // Procura por objeto com nome Peter
	.find({age:33}) // Procura por objeto com idade 33
	
	// Escolhendo campos
	.find({age:33},{ age:1 }) // Procura por objeto com idade 33, obtendo apenas a idade
	.find({age:33},{ age:0 }) // Procura por objeto com idade 33, obtendo todos os campos menos a idade
	
	// Cálculos na pesquisa
	.find({name:{ $eq: "Peter"}}) // Procura por objeto com nome igual a Peter
	.find({name:{ $ne: "Peter"}}) // Procura por objeto com nome diferente de Peter
	.find({name:{ $in: ["Peter","Mary"]}}) // Procura por objeto com nome igual a Peter ou Mary
	.find({name:{ $nin: ["Peter","Mary"]}}) // Procura por objeto com nome diferente de Peter e Mary
	.find({age:{ $gt: 20}}) // Procura por objeto com idade maior que 20
	.find({age:{ $gte: 20}}) // Procura por objeto com idade maior ou igual que 20
	.find({age:{ $lt: 20}}) // Procura por objeto com idade menor que 20
	.find({age:{ $lte: 20}}) // Procura por objeto com idade menor ou igual que 20
	
	.find({age:{ $gte: 20, $lte: 40}}) // age >= 20 && age <= 40
	.find({age:{ $gte: 20, $lte: 40}, name:"Mary"}) // (age >= 20 && age <= 40) && name == Mary
	
	.find({age:{ $exists: true}}) // objetos que contém a chave 'age'
	.find({age:{ $exists: false}}) // objetos que não contém a chave 'age'
	
	
	// Lógica booleana
	.find({$and: [{age: 18}, {name:"Mary"}] }) // age == 18 && name == Mary
	
	.find({$or: [{age: 18}, {name:"Mary"}] }) // age == 18 || name == Mary
	
	.find({ age: { $not: { $lte: 20 } }}) // !(age <= 20)
	/*
	OBS: O $not vai retornar todos que não possuem a chave 'age' também
	*/
	
	// Expressões com chaves
	.find({$expr: { $gt: ["$debt", "$balance"]} }) // debt > balance
	
	// Acessando campos
	/*
	Basicamente é necessário colocar o nome do campo dentro de aspas
	*/
	.find({"address.street": "123 Main St" }) // adress.street == "123 Main St"
	
	// Atualizando
	.updateOne({age:26}, { $set: {age: 27} }) // Atualiza o primeiro com idade 26 para 27

	$set // Seta o valor
	$inc // Incrementa o valor
	$rename // renomeia uma propriedade
	$unset // remove uma propriedade
	$push // Adiciona um elemento em um array
	$pull // Remove um elemento em um array
	
	// Remover e então Adicionar
	.replaceOne()
	
	// Remover
	.deleteOne()

MONGOOSE

Mongoose Crash Course - Beginner Through Advanced https://www.youtube.com/watch?v=DZBGEVgL2eE

Mongoose facilita o uso do mongodb. Permite definir um Schema

    # Instalar módulo
    npm i mongoose

Primeiramente, vamos fazer com que a nossa aplicação se conecte com o banco de dados MongoDB:

server.js

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/nomedobanco');

Basicamente, é necessário importar o módulo mongoose e então conectar na instância do MongoDB que está rodando no computador na porta 27017 (Porta padrão do MongoDB).

Schema

Além de conectar no banco é necessário definir um Schema, isto é, um modelo a qual os documentos irão seguir, definindo os campos que pode conter em cada documento de uma coleção.

Para ficar organizado, será criado um arquivo model dos artigos em ./models/artigo.js

./models/artigo.js

const mongoose = require('mongoose');

const artigoSchema = new mongoose.Schema({
    titulo: String,
    conteudo: String
}, { collection: 'artigos' }
);

module.exports = mongoose.model('artigos', artigoSchema, 'artigos');

Não pode esquecer de importar o modelo do artigo onde ele for ser usado, Será necessário modificar como a página de roteamento dos artigos funciona, primeiro veja como fica o arquivo da rota dos artigos utilizando MongoDB e então entederemos parte por parte:

./routes/artigo.js

const express = require('express')
const mongoose = require('mongoose');
const artigoModel = require('../models/artigo');

const router = express.Router()

router.get("/", async (req, res) => {   
    
    const artigos = await artigoModel.find();
    
    res.render("artigos", {artigos:artigos})
})

router.get("/new", (req, res) => {
    res.render("novoartigo")
})

router.post("/new", async (req, res) => {
    const novoArtigo = await artigoModel.create({
        titulo: req.body.titulo,
        conteudo: req.body.conteudo
    });

    res.redirect("/artigo/"+String(novoArtigo._id));
});

router.route("/:id")
.get(async (req,res) => {
    const artigo = await artigoModel.findOne({_id:req.artigoId});
    
    res.render("verartigo", {artigo:artigo})
})
.post((req,res) => {
    res.send("POST artigo "+String(req.artigoId));
})
.delete((req,res) => {
    res.send("DELETE artigo "+String(req.artigoId));
});

// por último
router.param("id", (req,res,next,id) => {
    if(mongoose.Types.ObjectId.isValid(id))
    {
        req.artigoId = new mongoose.Types.ObjectId(id);
        next();
    }
    else
    {
        res.status(400);
        res.send("<h1>Página não encontrada</h1><p>O valor informado para identificação do artigo não é válido</p>");
        return;
    }
})

// exportar o router para ser possível utilizá-lo no server.js
module.exports = router

Veja que ao utilizar o mongoose para acessar ou alterar um valor no banco, é necessário marcar a função da rota como async e realizar o await em cada operação, já que ela retorna uma promise, objeto do javascript que representa uma tarefa assíncrona que pode demorar uma quantidade arbitrária de tempo para ser executada.