Prática da Next Level Week #02 | Rocketseat
Esse repositório contempla a prática proposta na segunda edição da <nwl/>
(NextLevelWeek), cujo projeto se chama Proffy.
O nome Proffy é uma homenagem ao dia 06 de Agosto, Dia dos Professores.
O projeto chama-se Proffy e consiste numa plataforma de e-learning, onde se conecta alunos e professores numa plataforma de estudos online com opções de criar perfil, cadastro de matérias, horários e valores de aula, buscar por registro de professores (com filtros por horário e matéria) e agendar aulas (não contempla vídeo chamadas). Os dados serão salvos (storage) do lado do cliente. Utilizamos o conceito bubble first (mobile first), privilegiando os dispositivos mobile.
Antes de iniciarmos precisamos garantir que temos todas as ferramentas necessárias disponíveis em nosso ambiente local. A Rocketseat preparou um documento com o passo a passo para realizarmos essa configuração - seja para instalar as dependências, seja para atualizá-las, além de um documento com alguns dos erros mais comuns que podem ocorrer durante essa configuração.
Antes de prosseguir, execute os seguintes comandos no terminal, para garantir que estamos com a mesma versão do node e do npm:
node -v
// deve retornar v12.8.3
npm -v
// deve retornar 6.14.6
Garanta que possui acesso aos arquivos de layout do projeto no Figma para ter uma melhor visão do que será construído.
-
Rota de listagem de conexões realizadas (operação de soma dos registros)
-
Rota de criação de nova conexão (ativada quando clicarem em Entrar em contato)
-
Rota de listagem de aulas [filtros por matéria, dia da semana e hora]
-
Rota de Criação de nova aula
Confira as principais tecnologias utilizadas no projeto:
React é uma biblioteca de construção de interfaces SPA que via otimizar a experiência do usuário. Também existem 'sub pacotes' do React, de acordo com o ambiente da aplicação - veja alguns dos principais:
ambiente | 'sub pacote' |
---|---|
Web | ReactJS |
Mobile App | React Native |
Realidade Virtual | ReactVR |
Televisão | ReactTV |
Independentemente do 'sub pacote', sempre utilizaremos o React. O React também utiliza bibliotecas adicionais para integrar-se às interfaces de acordo com o ambiente. Por exemplo, na web utilizamos o ReactJS com o ReactDOM. Já para apps mobile utilizamos o [ReactNative][ReactNative] com uma biblioteca adicional também chamada de [ReactNative][ReactNative].
É uma plataforma que nos permite utilizar o mesmo JavaScript (puro ou com ReactJS, ...) no back-end.
-
Non-blocking IO: conseguimos controlar a assincronicidade
-
Streams (nos permite consumir dados no estilo 'LazyLoad') e WorkerThreads (nos permite trabalhar o core)
O TypeScript é uma extensão do JavaScript que utiliza a tipagem que facilita a manutenção do código e garante a consistência de nossas soluções, pois captura um erro antes mesmo de sua execução. Também possui recursos para IDEs que otimizam o desenvolvimento.
O Knex é um query builder, que possibilita escrevermos as queries de forma mais simples, em JavaScript.
O sqlite3 é um pacote de banco de dados mais leve, não requer instalações na máquina. Usaremos a sintaxe do knex para trabalharmos com o sqlite3.
Criaremos o projeto dentro da pasta do projeto/repositório. Caso utilize yarn execute o comando yarn create react-app web --template typescript
. Caso contrário, utilize o [npx][npx] com o comando npx create react-app web --template typescript
.
Após a criação do projeto (que chamamos de web), acesse a pasta com o comando cd web
e então abra o [VSCode][VSCode] (ou sua IDE de preferência) com o comando code .
. E, para visualizarmos o projeto rodando, rode yarn start
(ou npm start
).
Podemos remover os seguintes arquivos:
./web/README.md
./web/src/App.css
./web/src/App.text.tsx
./web/src/index.css
./web/src/logo.svg
./web/src/serviceWorker.ts
./web/src/setupTests.ts
Para que os erros que apareceram sejam corrigidos, precisamos:
-
Remover a chamada dos arquivos
serviceWorker.ts
eindex.css
no arquivoindex.tsx
-
Remover o método
unregister()
doserviceWorker.ts
do final do arquivoindex.tsx
(e os respectivos comentários) -
Remover a chamada dos arquivos
logo.svg
eApp.css
no arquivoApp.tsx
-
Substituir a tag
<header></header>
(e todas as demais tags contidas nela) por uma tag<h1></h1>
com o texto de preferência dentro dela -
Manter somente o arquivo
index.html
na pasta./public
e remover todos os demais arquivos -
No arquivo
index.html
, deixar apenas as tags meta charset, meta viewport, meta theme-color e title (atualizá-lo para Proffy, o nome da aplicação). Limpar também os comentários.
Vamos incluir a pasta disponível como material extra da aula #01 (pasta images
) dentro de src/assets
. Estamos importando para o projeto algumas imagens e ícones para utilizarmos ao longo do projeto. Estamos usando .svg
para que as imagens fiquem mais leves.
Crie o arquivo src/assets/styles/global.css
e aplique o inicial - incluindo as variáveis disponibilizadas nos arquivos extras da aula.
Crie o link do App.tsx
com o estilo global com import './assets/styles/global.css';
.
Importe as fontes Archivo (Pesos 400 - Regular -, e 700 - Bold) e Poppins (pesso 400 - Regular) do GoogleFonts com o seguinte trecho de código no index.html
:
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;700&family=Poppins&display=swap" rel="stylesheet">
index.tsx
Crie o arquivo src/pages/Landing/index.tsx
e inclua o trecho import React from 'react';
para importar o React.
No arquivo App.tsx
, altere o conteúdo da div#root
para <Landing></Landing>
(trazendo assim o componente Landing).
E então criaremos a função Landing()
, seguida do trecho que à torna disponível para os outros arquivos: export default Landing;
. E detalharemos a função Landing()
, incluindo logo, título, imagem, dois botões com ícones e um span - sempre usando o atributo className
para podermos estilizar esses trechos.
styles.css
Crie o arquivo src/pages/Landing/styles.css
para que possamos aplicar estilo especificamente no componente Landing, aplique o estilo e então inclua o caminho relativo para o estilo no index.tsx
: import './styles.css';
.
Vamos repetir o processo de criarmos novos componentes - TeachersList e TeachersForm.
Rotas
O primeiro passo é instalarmos o reactRouterDOM - basta executar yarn add react-router-dom
- ele nos ajudará a criarmos e manipularmos as rotas da aplicação.
Se instalado com sucesso, iremos criar o arquivo routes.tsx
na pasta ./src
- ele é um componente que conterá todas as nossas rotas. Também precisaremos rodar yarn add @types/react-router-dom -D
para que os componentes do react-router-dom
possam ser importados.
Devemos ainda importar o componente Routes no .App.tsx
e chamar o componente dentro da div.App
.
E no arquivo ./src/Landing/index.tsx
importamos Link
e alteramos as tags <a></a>
por <Link></Link>
(trocando os atributos href
por to
), para evitar loadings desnecessários.
Quebrando em Componentes
Criamos o conteúdo inicial do componente pages/TeacherList
. Mas, como podemos reaproveitar vários 'pedaços' dessa página em outras, tornaremos esses elementos repetitivos diferentes componentes, como, por exemplo, o components/PageHeader
.
Basicamente salvaremos esses componentes reutilizáveis na página /components
e trocaremos o trecho de HTML pela tag do respectivo componente (definindo os devidos atributos). Lembre-se de verificar as importações (mover ou copiar os imports necessários para cada componente e importar o componente novo nos arquivos de onde foi removido e onde mais venha a ser utilizado).
Propriedades
Agora precisamos alterar o título de cada instância do componente PageHeader
. Para isso faremos os seguintes passos:
- Transformar a function
PageHeader()
em uma const (usando arrow function). Também definiremos que oPageHeader
é um componente do tipo Function Component (FC), que receberá a interfacePageHeaderProps
. E, como parâmetro único, receberemos props (que contém todas as propriedades declaradas na interface). Ficará assim:
const PageHeader: React.FC<PageHeaderProps> = props => {
// ...
}
- Definir uma interface (chamada
PageHeaderProps
) que conterá as props do componente - nessa interface definiremos sua tipagem:
interface PageHeaderProps {
title: string;
}
- Trocar o conteúdo do título pela prop (usando interpolação):
<strong>{props.title}</strong>
- No componente que importa o
PageHeader
, trocaremos o conteúdo do título pela prop title:
<PageHeader title="Que incrível que você quer dar aulas!"/>
Conferir no início do documento - Configuração de Ambiente (Instalação ou Atualização).
Criar a pasta server
, acessá-la pelo terminal e executar:
yarn init -y
Ou, se não estiver usando yarn
, rode npm init -y
.
Será criado o arquivo package.json
- que contém as principais informações sobre esse projeto e suas dependências.
Vamos criar o arquivo ./server/src/server.ts
, que será o principal arquivo da aplicação (será o primeiro a ser carregado, tudo partirá dele).
Usaremos TypeScript no projeto, por isso precisamos instá-lo com o comando yarn add typescript -D
ou npm install typescript -D
.
E precisamos criar o arquivo tsconfig.json
, executando yarn tsc --init
ou npx tsc --init
. Esse arquivo determina a forma que o TypeScript gerará o arquivo JavaScript.
Por fim, instalaremos outra dependência ( ts-node-dev
), com os comandos yarn add ts-node-dev -D
ou npm install ts-node-dev -D
. Essa dependência será usada apenas em ambiente de desenvolvimento, ficará 'rodando' e observando o script constantemente, refletindo atualizações e alterações do nosso código.
Precisamos incluir um script de start em nosso package.json
, para podermos rodar yarn start
ou npm start
e iniciarmos nosso projeto.
Acrescentaremos uma sessão scripts e, nela, o comando start. O trecho --transpile-only
evita que erros sejam verificados, facilitando o desenvolvimento. Já o trecho --ignore-watch node_modules
evita que aplicações de terceiros sejam convertidas. E --respawn
evita que fique iniciando e finalizando. Ele só encerra após o 'Control C' no terminal.
Deve ficar assim:
{
"name": "server",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "ts-node-dev --transpile-only --ignore-watch node_modules src/server.ts"
},
"devDependencies": {
"ts-node-dev": "^1.0.0-pre.56",
"typescript": "^3.9.7"
}
}
express
Instalamos o express (um "micro framework" com algumas funcionalidades prontas):
yarn add express
ou
npm install express
No server.ts
importamos o express e definimos as configurações básicas do express (const app, listen e o método get()). No método get()
simulamos o acesso a '/users' e retornamos um objeto como exemplo.
Utilizaremos o SQLite por conta de sua simplicidade, mas poderíamos utilizar outros sem problemas. Para instalarmos o pacote no projeto, precisamos executar: yarn add knex sqlite3
ou npm install knex sqlite3
. O knex é um query builder, que possibilita escrevermos as queries de forma mais simples, em JavaScript.
Depois da instalação, vamos criar a pasta src/database
, onde ficarão os arquivos referentes ao BD (Banco de Dados). O primeiro arquivo a ser criado será o connection.ts
. Nele importaremos o knex e o path (para facilitar o uso dos recursos das rotas). Então definiremos a const db, que - usando knex - definirá o tipo de banco (sqlite3), a connexão e, no caso do sqlite3, definiremos a propriedade useNullAsDefault
como true
.
Migration
O Knex (um query builder) é escrito em .js
e não .ts
, por isso precisaremos criar o arquivo ./server/knexfile.ts
. Nele faremos o seguinte:
import path from 'path';
module.exports = {
client: 'sqlite3',
connection: {
filename: path.resolve(__dirname, 'src', 'database', 'database.sqlite')
},
migrations: {
directory: path.resolve(__dirname, 'src', 'database', 'migrations')
},
useNullAsDefault: true,
};
A migration serve para facilitar o trabalho em equipe ao garantir uma mesma estrutura de Banco de Dados, replicando todo o 'passo a passo'/histórico das schemas com simples comandos.
Para criar as migrations, vamos salvar os arquivos dentro de ./server/src/database/migrations
. As migrations devem estar ordenadas de modo que a ordem de criação/manipulação dos dados e tabelas seja respeitado. Por isso ordenaremos com um prefixo com 2 dígitos e o nome descritivo da migration, como 00_create_users.ts
.
Como o knex não lê JavaScript, customizaremos os comandos de terminal para rodar as últimas migrations e para realizar o rollback das mesmas (no arquivo ./server/package.json
):
"knex:migrate": "knex knexfile --knexfile.ts migrate:latest",
"knex:migrate:rollback": "knex knexfile --knexfile.ts migrate:rollback"
Para rodar os comandos basta acrescentar yarn
ou npm
antes ( yarn knex:migrate
).
Como vamos trabalhar com o sqlite3, é recomenrável usar a extensão SQLite do VSCode. Selecione Open database para visualizar a tabela users criada e as 2 tabelas da migration criada.
Agora vamos criar as demais migrations (class_schedule, classes e connections).
Vamos criar o arquivo ./server/src/routes.ts
. E vamos mover o trecho em que definimos a rota app.get()
no arquivo server.ts
.
Para cada rota, teremos um método (GET, POST, DELETE) e um retorno (a listagem dos registros, os detalhes de um registro, a criação ou atualização de um novo registro ou a exclusão de um registro). Portanto as rotas são responsáveis por executar as queries no banco de dados de acordo com as requests e responses.
Podemos utilizar o Insomnia para visualizarmos melhor o consumo e manipulação dos dados do banco sqilte3. Faremos essas operações de forma a usar o await/async nas promises, a desestruturação de objetos e outros conceitos já vistos até esse ponto. Exemplos:
routes.get('/classes',async (request, response) => {
const allClasses = await db('classes');
return response.json(allClasses);
});
routes.get('/classes/:id',async (request, response) => {
const classesId = request.params.id;
const classesItem = await (await db('classes').where('id', classesId));
return response.json(classesItem);
});
routes.post('/users', async (request, response) => {
const {
name,
avatar,
whatsapp,
bio
} = request.body;
const insertedUser = await db('users').insert({
name,
avatar,
whatsapp,
bio
});
return response.send();
});
Schedules
Como salvamos os horários como string, precisaremos tratar esse dado. Para isso faremos o seguinte (no arquivo routes.ts):
{ ... },
const routes = express.Router();
interface ScheduledItem {
week_day: number,
from: string,
to: string
};
{ ... }
const classSchedule = schedule.map((scheduledItem: ScheduledItem) => {
return {
class_id,
week_day: scheduledItem.week_day,
from: convertHourToMinutes(scheduledItem.from),
to: convertHourToMinutes(scheduledItem.to)
};
});
}
Observação: a função convertHourToMinutes()
foi declarada em src/utils/convertHourToMinutes/convertHourToMinutes.ts
.
Por fim, vamos atualizar a operação do banco de dados dentro da const classSchedule
:
await db('class_schedule').insert(classSchedule);
Transactions
Vamos criar uma transaction ( trx
) para podermos realizar todas operações em paralelo, evitando executar um registro antes de outra query gerar um erro.
Para isso basta definirmos const trx = await db.transaction();
e substituir await db...
por await.trx...
. Antes de retornar nossa response, vamos commitar todas as queries guardadas em nossa transaction: await trx.commit();
.
Outro recurso bacana do uso das transactions é podermos realizar o rollback do que já estava pronto para ser commitado: await trx.rollback();
.
Tratativa de Erros
Vamos incluir um try/catch, para - caso ocorra algum erro - podermos capturar os erros e tratá-los e/ou comunicá-los.
Controllers
Vamos reproduzir uma arquitetura MVC, por isso vamos isolar controllers de models e views. Então vamos criar a pasta src/controllers/ e seus arquivos.
Basicamente apontaremos uma rota (_routes.ts) para um controller, que por sua vez, determinará que ação será executada, em que tabela, etc.. Faremos isso com a ajuda do knex.
Criaremos também o src/controllers/ConnectionsController.ts.
Permite o controle de requisições de outras origens (Corss Origin). Para isso baixaremos o pacote cors para o ambiente de desenvolvimento - yarn add cors
e yarn add @types/cors -D
e importaremos no arquivo server.ts.
Componentes são basicamente funções que retornam trechos de HTML. No React criamos componentes para reaproveitarmos código, de forma a tornar o uso de elementos que se repetem ao longo dos documentos mais fácil (ao invés de reescrevermos o elemento a cada repetição, usamos o componente).
Sempre que criamos componentes precisamos:
-
Iniciar o nome do componente com letra maiúscula, para não ser interpretado como uma tag HTML
-
Sempre que formos incluir o HTML dento do código JavaScript (.jsx, .tsx) precisamos importar o React, com o seguinte trecho:
import React from 'react';
-
Usar a extensão .tsx, pois estamos usando TypeScript
-
Criamos uma pasta com o nome do componente (de acordo com a organização do projeto e tipo de componente - page, component, etc.) e criamos o arquivo
index.tsx
e seu estilo -styles.css
. Depois simplesmente importamos e utilizamos a respectiva tag do componente importado.
Propriedades são uma forma de transmitirmos informações entre os componentes. Funcionam como as propriedades de um objeto JavaScript. Podemos defini-las numa interface (já definindo sua tipagem) e definirmos o componente como FC (FunctionComponent), de modo que receba as props definidas na interface e permita usarmos as propriedades desejadas dentro do HTML do componente.
Outra maneira de usarmos uma prop (sem termos de definir na interface) é através da propriedade children ( props.children
). Essa propriedade nos permite definir o conteúdo dentro do contexto onde o componente está sendo utilizado (como o form de TeacherList
dentro do PageHeader
).
State são os estados dos componentes. Sempre que precisamos alterar uma propriedade através da interação com o usuário, utilizamos as interfaces - que é montada a partir das informações que recebe (via JavaScript).
Para trabalharmos com as rotas (endereços da aplicação), instalamos o react-router-dom
( yarn add react-router-dom
) e importamos o BrowserRouter
e o Route
no arquivo routes.tsx
(que também é um componente):
import React from 'react';
import { BrowserRouter, Route} from 'react-router-dom';
import ComponenteDesejado from './local/ComponenteDesejado';
function Routes() {
return (
<BrowserRouter>
<Route
path="/" // Caminho da rota
exact={true} // Se a correspondência deve ser exata
component={HomeBanner} // Componente a ser exibido
/>
<Route path="/caminho" component={ComponenteDesejado} />
</BrowserRouter>
);
}
export default Routes;
E no arquivo ./src/ComponenteDesejado/index.tsx
importamos também o Link
- import { Link } from 'react-router-dom
- e alteramos as tags <a></a>
por <Link></Link>
(trocando os atributos href
por to
), para evitar loadings desnecessários. O Link
nos permite aplicar o conceito de SPA (Single Page Application), pois depois de carregado o conteúdo da página, não será carregado novamente.
Também precisaremos rodar yarn add @types/react-router-dom -D
para que os componentes do react-router-dom
possam ser importados.
É uma plataforma que nos permite utilizar o mesmo JavaScript (puro ou com ReactJS, ...) no back-end.
-
Non-blocking IO: conseguimos controlar a assincronicidade
-
Streams (nos permite consumir dados no estilo 'LazyLoad') e WorkerThreads (nos permite trabalhar o core)
express
Iremos instalar algumas dependências como o express (um "micro framework" que traz algumas funcionalidades prontas). Para instalarmos, usamos:
yarn add express
ou
npm install express
Após a instalação devemos importá-lo no nosso server.ts
( import express from 'express';
). Pode ser necessário executar yarn add @types/express -D
também (caso o pacote não venha com TypeScript definido, aí baixamos esses pacotes com a tipagem).
Rotas
No arquivo server.ts
, declaramos const app = express();
, definimos os métodos do app
e depois definimos a porta que o listen()
usará (como 3333), com app.listen(3333);
.
O arquivo deverá ficar assim, por enquanto. O método get()
retorna a const users nesse exemplo:
import express from 'express';
const app = express();
app.get('/users', (request, response) => {
const users = [
{ name: 'Fulano', age: 25 },
{ name: 'Ciclano', age: 52 },
];
return response.json(users);
});
app.listen(3333);
Recursos
São os trechos enviados após o endereço 'base' (como /users
no exemplo anterior).
Métodos
São os métodos usados nas requisições HTTP, como get, post, put e delete, usados para listar, incluir, atualizar ou deletar dados. Usaremos o Insomnia para visualizarmos, consultarmos e manipularmos esses dados. Se preferir, pode utilizar o Postman também.
Na ferramenta, basta criarmos uma request, selecionarmos o endereço (endpoint), o método (GET, POST, PUT, DELETE...) e, se necessário, passarmos os parâmetros. No caso, temos como endereço http://localhost:3333/users
e como método, post.
Para o express ler JSON, precisamos rodar
app.use(express.json());
após criarmos a const app.
Parâmetros
Há 3 tipos de parâmetros que podemos utilizar. São:
-
Request Body: normalmente são os dados para criação ou atualização de um registro. Acessamos através de
request.body
. -
Route Param: recursos da nossa rota (como
/users/12
, onde 12 seria o ID do usuário). Na rota é representado por/users:id
. Acessamos através derequest.params
. -
Query Param: parâmetros mais utilizados para paginação, filtros e ordenação. São exibidos na URL a partir do
?
após o recurso e concatenados com&
. Cada query param possui uma chave e um valor (exemplo:/users?offset=0&orderby=ASC&limit=25
). Acessamos através derequest.query
.
O Knex é um query builder, que possibilita escrevermos as queries de forma mais simples, em JavaScript.
Configuração do ambiente Instalação das dependências Atualização das dependências Erros mais comuns
Figma | Insomnia | node | npm | notion | Postman | React | ReactDOM | ReactJS | React Native
Rocketseat
YouTube | Twitter | Facebook | Instagram
Vamos nos conectar? Se quiser trocar idéias, experiências e figurinhas, entre em contato comigo!
Marcelo Diament | Desenvolvedor Front End Pleno e Instrutor Rocketseat | Github | LinkedIn