Backend for Dummies (Etapa 1)

João Victor Golias
Outsmart Digital
Published in
12 min readOct 29, 2018

Backend for Dummies’ é o nome do projeto de treinamento de backend da Outsmart (startup com desenvolvimento mobile e web). O objetivo da primeira parte é familiarização com a nossa linguagem de programação: Typescript. Entende-se que seja essencial para nós que o desenvolvedor tenha conhecimento de: tratamento de erros; manipulação de array; desconstrução de objetos; sintaxe relacionada a OOP; além do básico da linguagem. Esses conceitos serão tratados aqui, para um maior aprofundamento acesse a documentação ou o handbook do Typescript. Há bastantes exemplos durante esse texto (que podem ser encontrados aqui) e se recomenda fortemente que sempre os teste, modificando algumas implementações para um melhor aproveitamento :)

Setup dessa etapa

Node.js é um framework de programação em JavaScript, que permite o desenvolvimento de aplicações de network, por ser um executor de eventos assíncronos. Junto dele, usaremos o node package manager (npm), que é um gerenciador de dependência. Acesse a página do node e o instale com npm.

O uso do npm é simples: ele lê um arquivo chamado package.json que possui as nossas dependências; baixa-as e guarda-as em uma pasta chamada node_modules, além de criar o arquivo package-lock.json, que possui as informações relacionadas às bibliotecas instaladas. Para instalarmos uma biblioteca, rodamos o comando

npm install <package name>

Isso faz com que ela fique salva no atributo dependencies no arquivo JSON. Entretanto, às vezes, utilizamos uma biblioteca somente em desenvolvimento, sem precisar que ela seja adicionada no nosso projeto em produção. Por exemplo, fazemos isso com frameworks de testes automatizados e a biblioteca com as funções relacionadas à AWS, que já a contém em seu ambiente. O Node interpreta essa funcionalidade quando salvamos dependências no atributo devDependencies. Para adicionar diretamente lá quando baixamos, colocamos a flag --save-dev , assim:

npm install <package name> --save-dev

Por fim, é interessante se falar da flag -g . Ela faz com que a biblioteca seja instalada no node_modules do seu computador. Normalmente se usa isto porque queremos usar algum script dentro da biblioteca sem a instalar em nenhum projeto. No nosso caso, instalaremos globalmente o yarn, que é um gerenciador de dependências mais rápido e mais seguro que o npm (leia a página deles para mais informações):

npm install -g yarn

Para instalar uma biblioteca como dependencies e como devDependencies, usamos, respectivamente

yarn add <package-name>yarn add <package-name> -D

Iniciamos o projeto, rodando yarn init. O terminal fará várias ‘perguntas’ referentes ao projeto; não precisa se preocupar muito com isso, e, por enquanto, pode deixar os valores default, com exceção de entry point e vamos mudar para main.js. Vamos instalar o Typescript como dependencies e o ts-node e o @types/node(que possui as funções próprias do node com tipagem) como devDependencies

yarn add ts-node @types/node -D && yarn add typescript

Abaixo está o package.json até agora

Uma vantagem que o package.json nos traz é que conseguimos criar um script nosso para ser rodado no projeto. Acrescentemos o atributo scripts, que será um objeto com chaves, representando o nome do nosso script; e o valores, representando o comando a ser executado. Para rodar um arquivo Javascript em node, basta rodar node <path do arquivo>.js .Então, vamos supor que queremos sempre rodar o arquivo main.js; podemos declarar um script run-project que rode main.js, da seguinte forma:

Assim, sempre que rodarmos npm run run-project ou yarn run-project , esse script será executado: node main.js.

Antes de começar a usar o Typescript, vamos adicionar mais um arquivo no root do nosso projeto: tsconfig.json . Ele possui as configurações do nosso transpilador. Não vamos entrar em muitos detalhes sobre arquivo em si, mas essa é um configuração bem básica, para usar ES6 e commonjs.

Vamos rodar nosso primeiro programa em Typescript: helloWorld.ts.

Para transpilar um arquivo Typescript para Javascript, usamos o seguinte comando tsc <path do arquivo>.ts . Ele cria um arquivo no mesmo path, mas como formato js. Então, vamos mudar o nosso script run-project para ele rodar tsc main.ts && node main.js , para transpilar e já rodar o nosso projeto. Com isso, a mensagem Hello World deve aparecer no seu terminal.

Básico da linguagem

Vamos começar entendendo como se declaram as variáveis nessa linguagem. Há a distinção de dois ‘tipos’ de variáveis: const é o identificador das variáveis cujo valor não irá mudar durante aquele bloco de execução; let indica o oposto, permitindo que a variável seja sobrescrita. Para mostrar o tipo da variável, declaramo-na e colocamos o tipo depois de : . Veja abaixo:

Existem dois comparadores de igualdade em JS: == e === . Quando usamos o primeiro, o compilador tenta transformar (cast) a segunda variável para o tipo da primeira; e, então, faz a comparação. Isso faz com que alguns valores sejam ‘compatíveis’ (retornado true na comparação): 0 e false ; 1 e true ; '1' e 1 ; etc. Já o segundo operador leva em consideração o tipo das variáveis e já retorna false se elas forem de tipos diferentes. Para desigualdades, existem != e !== , que possuem um comportamento análago a== e === , respectivamente.

Funções são blocos de códigos que permitem a reutilização deles. Uma das formas de se declarar uma função é usando o identificador function seguido do nome dela e seus parâmetros. Para adicionar tipagem, a sintaxe é muito parecida com as variáveis, devendo a indicar depois de : .

Uma característica muito interessante no JS é que ele permite que sejam atribuídas funções como valores de variáveis. Abaixo, há um exemplo de como se fazer e usar isso.

Outra maneira de se declarar funções é utilizando a sintaxe chamada de arrow function: <variável> = ( <parâmetros> ) => { <implementação } . Veja abaixo:

Não entraremos em detalhes aqui, mas existe uma diferença ao se declarar uma função como arrow function ou usando o identificador function . Para mais detalhes, leia esse artigo.

Manipulação de array

Array , em JS, é a estrutura de dados que armazena vários elementos que não precisam ser necessariamente do mesmo tipo. A inicialização de um array em JS é bem simples e basta colocar a informação entre dois colchetes: [1, 'hi', true] . O tipo é bem simples de se declarar, em TS, podendo ser indiciado por um [] após o tipo dos elementos ou o identificador Array<tipo> . Conseguimos acessar cada um dos elementos do array através do seu index (que começa em 0) usando [<index>] .

Um array, na verdade, é uma classe em TS e por isso possui métodos e um atributo próprios. Esse atributo é o length que devolve a quantidade de elementos (tamanho) do array. Usando isto, conseguimos melhorar o nosso código acima.

Os métodos são diversos e bem interessantes. Vamos falar de 4 deles aqui, mas recomendamos que leia mais sobre isso. A maioria deles recebe uma função de callback que é aplicada em todos os elementos do array.

  • map: Recebe uma função do tipo (element, index, array) => any . O primeiro parâmetro que é passado é o elemento em si; o segundo é o index do elemento; e o terceiro é todo o array, em que está se aplicando o map. Retorna um array que contém os resultados da função de callback
  • filter: Recebe uma função do tipo (element, index, array) => boolean. Retorna os elementos do array cuja função de callback retorna true
  • sort: Recebe uma função do tipo (a, b) => number, sendo a b, representando dois elementos do array. Esse callback deve retornar um número negativo, um positivo ou um zero e devolve um array organizado como [<negativo>,<zero>,<positivo>] .
  • forEach: Recebe uma função do tipo (element, index, array) => void. Aplica-a para cada um dos elementos do array

Objetos

Objetos são estruturas de dados que possuem atributos com uma key e um value, permitindo que representemos de uma forma bastante intuitiva. Veja abaixo um objeto user

O acesso aos atributos pode ser feito usando-se um . seguido da key do atributo que desejamos ou usando [<string da key>] .

Objetos podem ser passados como parâmetros de funções e também retornados por elas. Há um jeito de colocar um tipo nas funções, que consiste em, basicamente, escrever o contrato do objeto (com as keys e o tipo de cada uma delas).

A tipagem dessa função ficou muito confusa. O próximo tópico é sobre classes e interfaces que ajudarão bastante nisso.

Conseguimos desconstruir um objeto em um parâmetro dessa função. Isso consiste em separar keys do objeto direto na declaração da função.

Podemos ainda fazer a decomposição do objeto, usando ...objeto . Isso permite que criemos novos objetos com as keys de outro e mais outras que acrescentarmos. Caso uma dessas novas seja igual a uma das keys do original, ela será sobrescrita. Veja abaixo:

Typescript e o básico de OOP

Object-Oriented-Programming (OOP) é um paradigma de programação cuja base é a representação de dados como se fossem objetos. Uma classe é a estrutura básica de OOP, permitindo criar atributos e métodos a objetos. Para instanciar uma variável de uma classe, basta chamar o seu construtor precedido do identificador new :const obj = new MyClass() . Abaixo, declaramos uma classe que representa um usuário da nossa aplicação.

No exemplo acima, mostramos o uso do identificador this . Ele permite que acessemos a informação do objeto que estamos usando dentro de métodos dele, permitindo que usemos atributos e outros métodos definidos na própria classe.

Podemos deixar o nosso construtor mais robusto e já inicializar os atributos da classe. Basta declarar constructor(<parâmetros>) {<lógica>} que a lógica atribuída a ele sempre será executada assim que a classe for instanciada. Vamos fazer com que o construtor da classe User já receba os parâmetros name e age . Abaixo há dois exemplos de como se fazer isso, sendo o segundo uma abreviação de sintaxe que o Typescript nos permite.

Uma das principais vantagens de OOP é o conceito de hierarquia. Podemos fazer com que uma classe herde atributos e métodos de outra, formando uma relação pai-filha entre elas. Para isso, utilizarmos extends seguido do nome da classe que queremos ‘estender’. Suponha que, em nossa aplicação, existem diversos tipos de usuários, um deles é o Student e o outro é o Professor . O estudante deve se cadastrar com nome, idade e a turma. Já um professor necessita de nome, idade e disciplina. Como eles possuem atributos em comum, isolamos eles na classe User e fazemos ambas serem filhas dela. Uma observação: se a classe pai possui um constructor customizado você, obrigatoriamente, deverá criar um constructor para a classe filha e instanciar a classe pai usando super(...)

Agora que vocês entendem o conceito de herança, vamos falar de encapsulamento. É uma maneira de proteger as informações das nossas classes de classes, funções e códigos externos. Há três identificadores para isso. public (que usamos até agora) permite que os métodos e os atributos sejam acessados (lidas e alteradas) por qualquer tipo de agente externo. protected só permite que classes filhas acessem essa informação, impedindo que seja, inclusive, lido por códigos externos. private restringe a informação somente para a própria classe.

Na nossa aplicação de mentira, é essencial que os usuários tenham um email , mas a sua modificação é proibida. Então, vamos criá-lo como private . A idade precisa ser um pouco mais restrita (nem todos podem ver as idades um dos outros) mas pode ser modificada com mais facilidade, então, vamos a declarar como protected . Além disso, vamos criar funções que permitam a leitura delas, chamadas de getters e fazer uma função que permite atualizar a idade, ou seja, um setter . Leia, com calma, o exemplo abaixo.

Não há uma maneira de se herdar duas classes de uma vez em Typescript. Entretanto há uma outra solução muito elegante, que o Java também possui: interface . Uma interface basicamente contém o contrato da nossa estrutura de dados, sem possuir quaisquer implementações. Além disso, não se pode declarar encapsulamento nele (tudo é public); porque possui simplesmente o esqueleto do objeto. Uma classe que implements uma interface deve, obrigatoriamente, fornecer todos implementações (no caso de métodos) e valores (no caso de atributos) dessa interface.

No nosso caso, teremos outro tipo de ‘usuário’ da nossa aplicação: Admin . Na verdade, ele não precisa ter nem nome nem idade, somente e-mail, por isso não é bem um usuário. Além disso, ele deve possuir uma implementação para os métodos sayHello( ) e sayGoodBye( ) , assim como Professor e Student (estamos acrescentando, aqui, mais um método para ambas). Para englobar isso, pensamos em criar uma estrutra Member que possui as informações em comum para os três (e-mail e os dois métodos). Mas como Professor e Student possui informações em comum (que estão na classe User ), vamos fazer um pequeno refactor no nosso código. Transformaremos User em uma interface e faremos Member como uma interface também, para que as classes Professor e Student consigam implementar ambas. Leia atentamente o exemplo abaixo. (sério, com calma)

Ao se utilizar implements e extends , existe outro comportamento muito útil, chamado de polimorfismo: a classe filha também é do tipo da classe pai. Abaixo, mostramos isso, usando um array do tipo Member[] e atribuindo a ele um Admin , Professor e Student .

Tratamento de erros

Há três palavras chaves relacionadas com o tratamento de erro: try , catch e throw . Podemos ‘jogar’ erros quando o usuário faz alguma operação que não deveria ou que possa causar problemas com a aplicação. Para isso, usamos o throw que nos permite devolver qualquer tipo de informação e encerra a continuação do algoritmo. Normalmente, utilizamos a classe Error , que é instanciada pelo construtor new Error(message: string) . Veja os exemplos abaixo para entender melhor:

O bloco try-catch serve para tratar os erros que acontecerem no meio do código. Quando algo é retornado como throw , ele pode ser acessado dentro do próximo catch e então tratado como você quiser. Vamos imaginar que sempre queremos mostrar o erro com uma mensagem default seguida da específica; podemos isolar essa lógica nesse bloco.

Manipulando arquivos JSON

JSON, ou Javascript-Object-Notation, é uma maneira de representar objetos que copia a sintaxe do javascript. Ele obrigatoriamente é formado por key do tipo string escritas com aspas duplas ( " ) e value do tipo string , number ou boolean . Conseguimos registar array simplesmente por colocando os objetos dentro de colchetes[ { ... } ] .

Vamos criar um objeto database que possua duas listas, uma com estudantes ( students ) e outra com professores ( professors ), mockadas com: new Student('Joao',22,'joao@appsimples.com', 'Class B') e new Professor('Pedro',21,'pedro@appsimples.com','Backend') .

Nosso arquivo ficaria:

Para ler um arquivo .json, podemos usar a função nativa require . Salvamos a informação de um arquivo assim: const file = require(<path>) . E, então, conseguimos acessar os atributos.

Para escrever um arquivo.json, vamos usar a função da biblioteca fs :

writeFile(
path: PathLike | number,
data: any,
callback: (err: NodeJS.ErrnoException) => void): void;

data é a informação que deve ser salva no arquivo; e callback é a função a ser executada se der algum erro na escrita do arquivo. Para passar um objeto para o formato json, usamos JSON.stringfy(value: any) ; e essa deverá ser a data salva no nosso arquivo. Vamos fazer a função que nos geraria o arquivo.json de exemplo ( json_example.json ).

Miscelânia

Ternário é uma maneira simples de se fazer uma condição, a sintaxe dele é:

<condição> ? <se true> : <se false >

Para entender isso, vamos refazer a nossa implementação das funções de exemplo do método sort de array. Veja:

Template Strings é uma sintaxe que permite escrever strings de uma maneira mais fácil, indicada por ` ` .Primeiro que ela irá garantir que a string criada tenha exatamente a mesma formatação que a escrevermos entre os back-ticks. Segundo que é possível de se fazer referência a valores do nosso código externo desde que isolemos entre ${...} . Veja:

Timestamp é uma maneira de se registrar datas. Basicamente, a data é armazenada como o tempo entre ela e 1 de janeiro de 1970, podendo ser expressa em segundos ou milisegundos (JS usa milisegundos). Conseguimos mexer com isso em JS usando a classe Data . Abaixo, há exemplos de algumas manipulações envolvendo isso.

Projeto — Etapa 1

Você tem acesso a dois arquivos JSON: users.json e usersWithError.json. O primeiro possui informações de usuários de uma aplicação; o segundo também, mas há um usuário com erro.

Uma informação interessante de se ter da aplicação é a quantidade de usuários, (a) usando o arquivo users.json, determine isso.

Uma feature que está indiretamente em vários momentos de nossa aplicação é uma lista de email do usuários. (b) Imprima-a em ordem alfabética.

Em quase todos os aplicativos que já fizemos, foi solicitada uma lista de usuários com algumas (ou todas) as informações deles mas com um ‘layout’ específico. ( c)Imprima os usuários em ordem alfabética, seguindo esse modelo:

{
USER <index do usuário>
NAME: <nome do usuário>
BIRTHDATE: <data de nascimento, no formato DD/MM/YYYY>
}

Percebemos que alguns usuários foram criados sem alguns parâmetros obrigatórios (como mostra o arquivo usersWithError.json). Quando o sistema ler um usuário em que isso aconteça, seria interessante que um erro fosse mostrado com as mensagens ‘There is an user with invalid name’, ‘There is an user with invalid email’ ou ‘There is an user with invalid birthdate’, para cada um dos casos possíveis. (d) Para isso, faça uma classe User que possua um construtor que receba todos os parâmetros obrigatórios de um usuário (nome, email e data de nascimento). Antes de criar um novo objeto, verifique se algum deles não está faltando; caso esteja, transmita o erro solicitado.

--

--