github blob

GITHUB BLOG

Programação Web, o que é?

bruno-valerohá 2 meses0 comentários

A Programação Web é a ciência por trás de tudo o que vemos e interagimos na internet. Desde simples páginas estáticas, como Landing Pages, até complexas aplicações dinâmicas, a programação web é o alicerce que sustenta a vasta rede de informações e serviços online que utilizamos diariamente.

Fundamentos da Programação Web

Em sua essência, a Programação Web envolve a criação de páginas, sites e aplicações que são executadas em navegadores web., utilizando uma combinação de HTML (linguagem de marcação), CSS (estilos) e JavaScript (linguagem de programação), os desenvolvedores web constroem interfaces que não apenas são visualmente atraentes, mas também funcionam de maneira eficiente e intuitiva para os usuários.

Ferramentas e Tecnologias Modernas

À medida que a internet evoluiu, também o fez a Programação Web, com o surgimento de novas ferramentas e tecnologias:

  • Frameworks Front-End: Como o React.js, Angular e Vue.js, que simplificam o desenvolvimento de interfaces complexas e interativas.

  • Frameworks Back-End: Como o Node.js, Django e Ruby on Rails, que permitem a criação de aplicações web robustas e escaláveis, gerenciando o lado do servidor e a lógica de negócios.

  • Bancos de Dados: Como MySQL, PostgreSQL e MongoDB, essenciais para armazenar e gerenciar dados em aplicações web.

  • Protocolos e APIs: Como HTTP/HTTPS e RESTful APIs, GraphQL, Web Socket, que facilitam a comunicação entre diferentes sistemas e serviços na web.

Exemplo Prático

Usarei meu projeto gerenciador de hábitos como exemplo. O Front-End foi construído com Next.js que conecta à um REST API em Node.js usando o micro-framework Fastify.

Node.js - Back-End

Primeiro abordarei a parte do Back-End que está disponível publicamente no meu GitHub, o conteúdo mostrado aqui pode ser encontrado nas pastas src/infra/app.ts, src/infra/applications/habits-tracker/routes/users.ts e src/infra/applications/habits-tracker/routes/application.ts.

Para possibilitar a execução de testes end-to-end é necessário separar a implementação das funcionalidades da api da conexão dela com o exterior, para isso eu crio um arquivo chamado app.ts que irá conter as funcionalidades, a conexão com banco de dados e a lógica das regras de negócio. Então crio outro arquivo chamado server.ts que fará a conexão com o exterior atravéz do método listen. Abaixo está o conteúdo do app.ts

Node.js - Realizando as importações necessárias

As importações são essenciais para organizar o código, facilitar a reutilização de componentes e funções, evitar a poluição do arquivo com código em excesso, e garantir uma estrutura escalável de fácil mantenção. Elas ajudam na localização rápida de recursos, melhoram a integração com ferramentas de desenvolvimento e permitem uma gestão eficiente de dependências, contribuindo para um desenvolvimento mais eficiente e organizado.

import { readFileSync } from 'node:fs' import fastifyCookie from '@fastify/cookie' import cors from '@fastify/cors' import fastifyJwt from '@fastify/jwt' import fastify from 'fastify' import { ZodError } from 'zod' import { DataValidationError } from '@/core/errors/errors/data-validation-error' import { application } from './applications/habits-tracker/routes/application' import { users } from './applications/habits-tracker/routes/users' import { env } from './env'

Node.js - Instanciando o Fastify

Fastify é um framework web para Node.js conhecido por sua velocidade e eficiência. Projetado para lidar com cargas intensivas, oferece alto desempenho com baixo consumo de recursos. Inclui suporte a plugins, validação de entrada, logging integrado e é baseado em TypeScript para desenvolvimento seguro. É uma escolha robusta para construir APIs rápidas e escaláveis.

Saiba mais sobre o framework Configurando um servidor com Fastify. ou consultando sua documentação

export const app = fastify({ logger: true })

Node.js - Configurando o cors

CORS (Cross-Origin Resource Sharing) é uma política de segurança que os navegadores aplicam para controlar como recursos de um site podem ser acessados por outro site. Ela protege contra acesso não autorizado a APIs e informações sensíveis, definindo quais origens podem fazer solicitações e quais métodos HTTP são permitidos. Essa medida ajuda a prevenir ataques e garante um ambiente web mais seguro.

A documentação do MDN oferece mais informações sobre o cors.

app.register(cors, { origin: [ `http://localhost:${env.PORT}`, 'http://localhost:3000', 'https://habits.brunovalero.com.br', ], allowedHeaders: [ 'Origin', 'x-requested-with', 'accept', 'content-type', 'authorization', ], methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], credentials: true, })

Node.js - Adicionando autenticação por JWT

JWT (JSON Web Token) é um formato padrão para transmitir informações entre partes de forma segura como um objeto JSON. Usado principalmente para autenticação, ele consiste em três partes: cabeçalho, carga útil e assinatura. O JWT é gerado pelo servidor ao autenticar um usuário e é enviado de volta ao cliente, que o armazena e o envia em requisições subsequentes para autenticação. Ele é seguro porque é assinado digitalmente e pode conter informações úteis, como identificação de usuário e autorizações.

app.register(fastifyJwt, { secret: { private: readFileSync('./keys/private_key.pem', 'utf8'), public: readFileSync('./keys/public_key.pem', 'utf8'), }, sign: { algorithm: 'RS256' }, cookie: { cookieName: '@habit-tracker:user', signed: false, }, })

Node.js - Integrando o plugin de Cookies

Cookies são pequenos arquivos de texto armazenados no navegador do usuário. Eles servem para manter sessões de usuário, armazenar preferências e rastrear informações de navegação. Os cookies podem ser de sessão (temporários) ou persistentes (permanecem até expirarem ou serem excluídos). São importantes para personalizar experiências online, mas levantam questões de segurança e privacidade. Os usuários podem controlar cookies nas configurações do navegador, e há regulamentações como o GDPR que exigem consentimento para o uso de cookies não essenciais.

app.register(fastifyCookie)

Node.js - Adicionando os plugins criados por mim para integrar as rotas da aplicação

Rotas em uma API são URLs que definem como os recursos podem ser acessados e manipulados. Cada rota corresponde a um endpoint específico, como /usuarios para listar usuários. Métodos HTTP como GET (para obter dados), POST (para criar), PUT/PATCH (para atualizar) e DELETE (para excluir) são usados para operações sobre esses recursos. As rotas podem incluir parâmetros dinâmicos, como IDs de recursos. É importante documentar e garantir segurança nas rotas, usando práticas como autenticação via tokens JWT.

// rotas gerais da aplicação app.register(application, { prefix: '/habits-tracker' }) // rotas relacionadas com os usuários app.register(users, { prefix: '/habits-tracker' })

Node.js - Adicionando um gerenciador de erros

Gerenciar erros em uma API envolve definir padrões claros de resposta para diferentes tipos de problemas, como erros de validação ou problemas de servidor. É essencial usar códigos de status HTTP apropriados e mensagens de erro descritivas para ajudar os desenvolvedores a diagnosticar e corrigir problemas rapidamente. O tratamento de exceções deve ser feito de forma robusta e centralizada, com logging detalhado dos erros para análise posterior. Documentar bem os tipos de erros e como lidar com eles é fundamental para garantir uma API confiável e de fácil uso.

app.setErrorHandler((err, _req, res) => { if (env.NODE_ENV !== 'production') { console.error(err) } if (err instanceof ZodError) { return res .status(400) .send({ message: 'Validation error', issues: err.format() }) } if (err instanceof DataValidationError) { return res .status(400) .send({ message: 'Validation error', issues: err.message }) } return res.status(500).send({ message: 'Internal server error.' }) })

Node.js - Rotas da aplicação

No trecho "adicionando os plugins criados por mim para integrar as rotas da aplicação" há duas conexões com as rotas. O método register do Fastify adiciona um plugin que pode ser customizado por você, neste caso eu criei dois plugins que conectam o framework com as rotas. A seguir veremos o conteúdo desses plugins.

Node.js - Rotas de usuário (plugin "users")

Este plugin contém todas as rotas que tem relação com o usuário, como login, cadastro e autenticação. Como observado há 3 rotas disponíveis: POTS para "/users/register/jwt", POTS para "/users/authenticate/jwt" e GET para "/users". E cada uma possui um controller para gerenciar as requisições direcionadas a ela.

import { FastifyInstance } from 'fastify' import { authJwtController } from '../http/controllers/user/auth-jwt-controller' import { getUserController } from '../http/controllers/user/get-user-controller' import { registerJwtController } from '../http/controllers/user/register-jwt' import { jwtVerify } from '../http/middlewares/jwtVerify' export async function users(app: FastifyInstance) { app.post('/users/register/jwt', registerJwtController) app.post('/users/authenticate/jwt', authJwtController) app.get('/users', { onRequest: [jwtVerify] }, getUserController) }

Node.js - Rotas de funcionalidades (plugin "application")

Este plugin contém todas as rotas que tem relação com as funcionalidades principais da aplicação, como criação, listagem, remoção e atualização de entidades. Há várias rotas relacionadas aos hábitos, várias relacionadas aos dias e uma realcionada aos hábitos do dia. Cada uma possui um controller para gerenciar as requisições direcionadas a ela.

import { FastifyInstance } from 'fastify' import { createDayController } from '../http/controllers/habit-tracker-controllers/days/create-day-controller' import { fetchDaysWithCompletedNumberController } from '../http/controllers/habit-tracker-controllers/days/fetch-days-with-completed-number-controller' import { findDayDetailsController } from '../http/controllers/habit-tracker-controllers/days/find-day-details-controller' import { toggleDayHabitController } from '../http/controllers/habit-tracker-controllers/days/toggle-day-habit-controller' import { createHabitController } from '../http/controllers/habit-tracker-controllers/habits/create-habit-controller' import { deleteHabitController } from '../http/controllers/habit-tracker-controllers/habits/delete-habit-controller' import { fetchHabitsController } from '../http/controllers/habit-tracker-controllers/habits/fetch-habits-controller' import { findHabitController } from '../http/controllers/habit-tracker-controllers/habits/find-habit-controller' import { jwtVerify } from '../http/middlewares/jwtVerify' export async function application(app: FastifyInstance) { app.addHook('onRequest', jwtVerify) // habits app.post('/habits', createHabitController) app.delete('/habits/:id', deleteHabitController) app.get('/habits/:id', findHabitController) app.get('/habits', fetchHabitsController) // days app.post('/days', createDayController) app.get('/days/:date/details', findDayDetailsController) app.get('/days/completed-details', fetchDaysWithCompletedNumberController) // day-habits app.patch('/toggle-day-habit', toggleDayHabitController) }

Node.js - Controllers

Os controllers são componentes que gerenciam requisições HTTP em uma aplicação, conectando o cliente à lógica de negócio através dos use cases. Eles recebem requisições, chamam os use cases correspondentes para processar os dados e retornam respostas apropriadas para o cliente. Essa separação de responsabilidades ajuda na organização do código, facilitando a manutenção e promovendo uma arquitetura modular e escalável.

Há mais informações sobre os controllers no artigo Node.js: Entendendo Controllers em NestJS, embora tem foco em outro framework, o conceito de controller é o mesmo.

Next.js - Front-End

Agora abordarei a parte do Front-End, mostrando como integrá-lo com as rotas do servidor Node.js que acabamos de conhecer.

A aplicação Front-End possui somente uma rota, porém é altamente interativa e possui diversos componentes do React para manter tal interatividade, por isso passarei brevemente por seu conteúdo, pois já tem muita informação para digerir.

Eu usei algumas ferramentas para me ajudar no desenvolvimento, como o Tailwindcss para estilização, o Tantack Query para gerenciar as requisições ao servidor, e o shadcn/ui para obter componentes genéricos com responsividade e acessibilidade configuradas.

Mais informações sobre o Tailwindcss estão disponíveis em sua documentação. A documentação do Tanstack Query pode te ajudar a entender melhor esta ferramenta. Consulte a documentação do shadcn/ui para saber mais sobre o pacote.

Next.js - Página Home

A página home irá conter todo o conteúdo da aplicação, eu a dividi em três partes: o Header, o DateTrackerComponent, e o Credits

import { Credits } from '@/components/credits' import { DateTrackerComponent } from '@/components/date-tracker-component' import { Header } from '@/components/header' export default function Home() { return ( <main className="flex h-full min-h-screen w-full min-w-[100vw] flex-col items-center justify-center bg-background"> <div className="flex w-full max-w-5xl flex-col items-center justify-center gap-16 px-16 max-[550px]:my-20 max-[550px]:max-w-[100vw] max-[550px]:px-3 max-[550px]:pr-5"> <Header /> <DateTrackerComponent /> <Credits /> </div> </main> ) }

Next.js - Componente dos Dias do Ano

Este componente é responsável por enviar as propriedades para cada um dos quadradinhos da aplicação e por manter toda a lógica de funcionamento da quantidade de dias que deve estar visível, dê uma olhada para entender melhor. O código completo do trecho abaixo está disponível publicamente no meu GitHub em src/components/date-tracker-component/year-days/index.tsx

Next.js - Deixar 'use client' explícito

Por padrão, os componentes do Next.js são renderizados pelo lado do servidor, fazendo que que o conteúdo da página seja estático. Isto é excelente para otimizar a velocidade de carregamento da página, pois o HTML já vem pronto e não precisa ser renderizado em tempo de execução.

Porém para o propósito desta aplicação isso não é útil, pois a proposta dela é de ser interativa e dinâmica, então não pode ser estática. Para tornar uma página interativa no Next.js é necessário que o componente seja renderizado do lado do cliente, não do lado do servidor, para que o HTML seja renderizado em tempo de execução. Por isso deixamos no topo da ágina uma string contendo 'use client', para informar ao Next.js que este componente deve ser renderizado no lado do cliente.

'use client'

Next.js - Realizar as importações

Elas são necessárias para usar pacotes de terceiros ou outros módulos criados por você, possui outros benefícios explicados anteriormente.

import { useQuery } from '@tanstack/react-query' import { eachDayOfInterval } from 'date-fns' import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' import { useCallback, useEffect, useMemo, useRef } from 'react' import { fetchDaysWithCompletedNumber, FetchDaysWithCompletedNumberResponse, } from '@/api/fetch-days-with-completed-number' import { DaySquareProps } from '@/components/day-square' import { QueryKeys } from '@/components/providers/query-client' import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area' import { YearDay } from './year-day'

Next.js - Configura o dayjs para trabalhar com o formato UTC

Datas em UTC (Tempo Universal Coordenado) são um padrão global para sincronização de tempo sem um fuso horário específico. São usadas para garantir consistência em sistemas distribuídos ao redor do mundo, evitando problemas com fusos horários diferentes. UTC é preciso e confiável, essencial para registros precisos em sistemas críticos. Na programação, é comum converter UTC para o fuso horário local para exibição ao usuário final, mantendo a consistência global.

dayjs.extend(utc)

Next.js - Ref do scroll e dateRange

A ref do scroll é obtida à partir do Hook useRef do React e será usado para navegar até o fim do scroll assim que o componente carregar, a fim de enviar o usuário para a data mais atual desde o começo.

O dateRange usa o Hook useMemo do React e seu objetivo é calcular a data desde o início do ano atual até a data de hoje, porém a data inicial deve ser num Domingo e se a distância entre a data atual e a data de hoje forem menor do que 18 semanas, ele irá ultrapassar o início do ano buscando datas do ano anterior até que haja pelo menos 18 semanas de diferença e o dia inicial seja Domingo.

export function YearDays() { const scrollRef = useRef<HTMLDivElement>(null) const dateRange = useMemo(() => { const dateToUTC = (date: Date) => new Date( date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), ) const startOfYear = dayjs.utc().startOf('year') const firstDay = dateToUTC( startOfYear.subtract(startOfYear.day(), 'day').toDate(), ) const today = dateToUTC(dayjs.utc().startOf('day').toDate()) const minDayDifference = 18 * 7 // 18 semanas const dayDifference = dayjs.utc(today).diff(firstDay, 'day') const todayMinusMinDiff = dayjs.utc(today).subtract(minDayDifference, 'day') const minDateFirstDay = dateToUTC( todayMinusMinDiff.subtract(todayMinusMinDiff.day(), 'day').toDate(), ) const isMoreThanMinDayDifference = dayDifference > minDayDifference const startDay = isMoreThanMinDayDifference ? firstDay : minDateFirstDay console.log('dateRange', 'startDay', startDay, 'today', today) return { from: startDay, to: today, } }, [])

Next.js - Busca os Dias com Tanstack Query

Realiza a requisição para o servidor buscando e salvando em cache todos os dias criados pelo usuário logado

const { data: daysResponse } = useQuery({ queryFn: () => fetchDaysWithCompletedNumber({ from: dateRange.from, to: dateRange.to, }), queryKey: [QueryKeys.FETCH_DAYS], })

Next.js - yearDays

Busca todos os dias do intervalo calculado pelo dateRange e verifica quais dias desse intervalo estão na mesma data que os dias buscados no servidor e retorna um objeto de formato padrão.

const yearDays = useMemo(() => { const interval = eachDayOfInterval({ start: dateRange.from, end: dateRange.to, }).map((item) => { const getStartOfDay = (date: Date) => dayjs.utc(date).startOf('day').toDate() const dayStart = getStartOfDay(item) const currDay = daysResponse?.days?.find((day) => { return getStartOfDay(day.date).toISOString() === dayStart.toISOString() }) if (!currDay) return { id: '', date: dayStart, updatedAt: null, totalDayHabits: 0, completedDayHabits: 0, } return currDay }) return interval }, [dateRange, daysResponse])

Next.js - Lógica do Ref do scroll

Implementa a lógica do Ref do scroll pra que o usuário seja levado até a data mais recente desde o início.

useEffect(() => { const time = setTimeout(() => { if (scrollRef.current) { scrollRef?.current?.scroll({ left: scrollRef?.current?.scrollWidth, behavior: 'smooth', }) } }, 50) return () => clearTimeout(time) }, [])

Next.js - Função handleYearDayState

Cria uma função com o Hook useCallback do React que tem o objetivo de obter a porcentagem de tarefas do dia completas em relação ao total de tarefas programadas para aquele dia. Depois retorna um número de 0 à 5 onde 0 representa que nenhuma tarefa foi cumprida e 5 representa que todas as tarefas foram cumpridas.

const handleYearDayState = useCallback( (data: FetchDaysWithCompletedNumberResponse['days'][number]) => { const completedDayHabits = data.completedDayHabits const totalDayHabits = data.totalDayHabits const numberState = Math.round( (completedDayHabits / (totalDayHabits || 1)) * 5, ) const state = String(numberState) as DaySquareProps['state'] return state }, [], )

Next.js - Retorno JSX do componente

Por fim, retorna o JSX para renderizar o componente, que passa a scroll ref para o compoenente de scroll e faz um loop percorrendo todos os dias do período calculados pelo yearDays e retornando o componente "YearDay", responsável por renderizar cada quadradinho que representa um dia, passando o resultado da função handleYearDayState como parâmetro, que determinará a cor do quadrado .

return ( <ScrollArea ref={scrollRef} className="w-full overflow-x-auto pb-4"> <div className="grid grid-flow-col grid-rows-7 gap-3"> {yearDays.map((item) => ( <YearDay key={item.date.toISOString()} date={item.date} state={handleYearDayState(item)} /> ))} </div> <ScrollBar orientation="horizontal" scrollAreaThumb={{ className: 'bg-muted' }} /> </ScrollArea> ) }

O que você aprendeu?

Com isso você teve uma base sólida sobre os Fundamentos da Programação Web, com exemplos de Back-End usando Node.js e Front-End usando Next.js.

Este conteúdo foi útil? Deixe seu comentário.