Miniminalismo com Node
Tags: node, javascript, tutorial, programming
Published: 2022-07-21 02:57:28


![Tao symbol](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b7kspeo0izhx0rv75t8h.png)


## Referências

Antes de começar esse resumo do Tao of Node do Alexander Kondov, caso queria a fonte original de muito do que eu falo, seguem elas aqui:

* [Tao of Node](https://alexkondov.com/tao-of-node/)
* [O que é o Event Loop ? - Philip Roberts](https://www.youtube.com/watch?v=8aGhZQkoFbQ)
* [Dentro do Event Loop - Jake Archibald](https://www.youtube.com/watch?v=cCOL7MC4Pl0)

Com essas referências eu acredito que teremos alguma base para o que falaremos hoje nesse blogpost.
O projeto que eu usei como modelo é esse [daqui](https://github.com/Grubba27/crud-fastify)

## No começo

Independente que tipo de projeto você irá fazer em node, vamos falar um pouco sobre filosofia, em node temos uma ideia que o pequeno é bonito, apenas o necessário. Minimalista. O que isso gera ? temos pequenos pacotes ou modulos que fazem algo muito bem feito e provavelmente é mantido pela comunidade. Sim o NPM ou Yarn é algo que faz parte da filosofia do Node e seus pacotes levam isso consigo. Express é o maior exemplo e por sua vez é quase que sinônimo de node, TypeScript por ser literalmente JavaScript com tempero também é muito bem aceito... React e muitos outros são apenas JS com temperinho mas um tempero muito bem feito.

## Setup

Óbvio que como criamos um projeto em 2022, usaremos TypeScript que é uma solução para lidarmos com o aumento de nossa codebase, também usaremos fastify, mais por opção propria por gostar da filosofia deles e de ter algumas coisas out of the box mas express ainda é o grande framework/lib do node. 

Também gosto de ressaltar que por preferência uso o MongoDB, mas isso é mais detalhe de como serão guardados do que sobre como seu código é estruturado.
Cada modelo ou domínio da aplicação deverá ter seu próprio diretório e seguir lá com suas complexidades, deixando assim o mais simples e fácil visualização. No exemplo temos apenas dois domínios em nossa aplicação de petshop, os Pets e os Customers:

![Domains](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rnyc303kbfb1y096qu5d.png)
 

## Controllers

Quando falamos de controllers elas são a nossa fachada, onde o front bate, pede ou simplismente mexe, é a nossa API. Quando se pensa em API ela tem que ser simples mas ao mesmo tempo eficiente em seu trabalho, fazer o que se precisa. Nesse crud minha fachada de Customer ficou dessa forma:

```ts
export async function CustomerController(fastify: FastifyInstance) {


    const customerService = CustomerService(fastify);
    const petService = PetService(fastify);

    fastify.get<{ Reply: Array<CustomerSchema> }>
    ('/customers',
        async (
            request: FastifyRequest, reply: FastifyReply
        ) => {
            const result = await customerService.getAllCustomers()
            if (result.length === 0) {
                reply.status(404);
                throw new Error('No documents found')
            }
            reply.status(200).send(result);
        });

    fastify.get<{ Params: { customerID: string }, Reply: CustomerSchema }>
    ('/customers/:customerID',
        async (
            request: FastifyRequest<{ Params: { customerID: string } }>,
            reply: FastifyReply
        ) => {
            const {customerID} = request.params;
            const result = await customerService.getCustomerById(customerID);
            if (!result) {
                reply.status(404).send(customerID);
                throw new Error('Invalid value');
            }
            reply.status(200).send(result);
        });

    fastify.get<{ Params: { customerID: string }, Reply: CustomerSchema }>
    ('/customers/:customerID/pets',
        async (
            request: FastifyRequest<{ Params: { customerID: string } }>,
            reply: FastifyReply
        ) => {
            const {customerID} = request.params;
            const customer = await customerService.getCustomerById(customerID);

            if (!customer) {
                reply.status(404).send('Invalid user id');
                throw new Error('Invalid user id');
            }

            if (customer.pets === undefined || customer.pets?.length === 0) {
                reply.status(400).send('No pets were added');
                throw new Error('No pets were added');
            }

            const res = await petService.getPetsByIds(customer.pets).toArray();

            if (res === null) {
                reply.status(500).send('DB broke');
                throw new Error('Something is wrong');
            }
            reply.status(200).send(res);
        });

    fastify.put<{ Body: CustomerSchema, Reply: CustomerSchema, Params: { customerID: string } }>
    ('/customers/:customerID',
        async (
            request: FastifyRequest<{ Body: CustomerSchema, Params: { customerID: string } }>,
            reply: FastifyReply
        ) => {
            const {customerID} = request.params;
            const customer = request.body;
            const result = await customerService.updateCustomer(customerID, customer);
            if (result.ok === 0) {
                reply.status(400).send(customer);
            }
            reply.status(200).send(customer);
        });

    fastify.post<{ Body: CustomerSchema, Reply: CustomerSchema }>
    ('/customers',
        async (
            request: FastifyRequest<{ Body: CustomerSchema, Reply: CustomerSchema }>,
            reply: FastifyReply
        ) => {
            const customer = request.body;
            const createdCustomer = await customerService.createCustomer(customer);
            reply.status(200).send(createdCustomer);
        });
}
```

Vendo essa controller podemos inferir algumas coisas, diferente mas muito similar á um projeto em uma linguagem orientada a objetos, temos uma injeção de dependencia no começo dela, quando chamamos os dois services, e toda controller acontece num contexto de uma Função.

Única responsabilidade da controller é controlar o fluxo, chamar as funções e dai retornar erro ou os dados, sem acessar regra de négocio / Banco de dados.

Vamos seguir a ordem das partes lógicas do código, por próximo, iremos falar da service e o que ela deve ter de responsabilidade.

## Services

Quando se fala em services, pensa em duas partes, quem chama o banco de dados ou context  e lidar com regras de negócio. No caso de um projeto simples como esse a service chama o DB e lida as gravações apenas.

```Ts
export default function PetService(
    fastify: FastifyInstance<Server, IncomingMessage, ServerResponse, FastifyLoggerInstance>
) {
    const db = PetContext(fastify);

    const getAllPets = () => {
        return db.find().toArray();
    }

    const getPetById = (id: string) => {
        return db.findOne(new ObjectId(id))
    }

    const getPetsByIds = (ids: Array<string>) => {
        const i  = ids.map($ => new ObjectId($));
        return db.find( {_id: {$in: i}} );
    }

    const updatePet = (id: string, pet: PetSchema) => {
        return db.findOneAndReplace({_id: new ObjectId(id)}, pet);
    }

    const createPet = (pet: PetSchema) => {
        return db.insertOne(pet);
    }

    const deletePet = (id: string) => {
        return db.deleteOne({_id: new ObjectId(id)});
    }

    return {getAllPets, getPetById, updatePet, createPet, getPetsByIds, deletePet}
}
```

Como pode ser visto no código acima, esse service é um conjunto de funções que por sua vez recebem no parametro o código que será guardado no banco de dados.

## Context

O context ou banco de dados é o arquivo onde lidaremos com isso. O arquivo `pet-context` é nada mais que um arquivo onde nosso foco é conectar com nossa fonte de dados e dar um tipo ou schema a ela.

```ts
export default function PetContext(fastify: FastifyInstance<Server, IncomingMessage, ServerResponse, FastifyLoggerInstance>) {
    if (fastify.mongo.db !== undefined) {
        return fastify.mongo.db.collection<PetSchema>('Pets');
    }
    throw new Error('No DB collection found')
}
```

Simples não ? é por ser mongo e muito da complexidade estar no schema, mas as migrations e outras tarefas relacionadas a dados deveriam estar nesse context,ou seja em um diretorio onde exporta apenas o DB e as suas funcionalidades ficam escondidas, nesse caso é só a exportação da collection.

## Schema

Schema é a representação do teu dado, pode ser um type + Objeto, é onde a base do teu dominio irá residir, se você tem um schema no banco e alguns outros detalhes, tudo isso ficará dentro desse diretório. O importante é ser claro para quem toca no projeto os dominios e a possibilidade de extensão através de diretorios e arquivos.

sem mais delongas o Schema de pet:

```ts
export const Pet = Type.Object({
    name: Type.String(),
    type: Type.Optional(Type.String()),
    ownerID: Type.Optional(Type.String()),
});
export type PetSchema = Static<typeof Pet>;
```

Olha só, temos um Pet que é o schema do banco e o type dele que é usado pelo TypeScript. É essa simplicidade que tem que ser buscada em projetos de node, simples, fazendo apenas uma coisa, mas fazendo essa única coisa muito bem.

## Resumo

Em resumo, devemos olhar com simplicidade e minimalismo com nossos backends, não buscar criar mais código que o necessário, sempre tentar deixar a entropia do código próxima de zero, para que a manutenção seja possivél.

Recomendo a leitura dos links dispostos no começo, uma vez que a fonte original por mais que seja um pouco mais difícil é o conteudo em natura e muitas vezes mais eficiente para o aprendizado.