/blogs-api

Projeto desenvolvido durante o módulo de Backend na Trybe.

Primary LanguageJavaScript

Projeto Blogs API

O que foi desenvolvido

Neste projeto foi desenvolvido uma API e um banco de dados de conteúdo para um blog!

Uma aplicação em Node.js usando o ORM Sequelize para fazer um CRUD de posts.

  1. Foi utilizado o Docker, para o desenvolvimento.

  2. Foi desenvolvido endpoints que estão conectados ao banco de dados seguindo os princípios do REST;

  3. Foi desenvolvido uma camada de autenticação de pessoas usuárias.

  4. E também foi criado o relacionamento de categorias para os posts, trabalhando, assim, a relação de posts para categories e de categories para posts.

Preview:

  • Rodando as Migrations e Seeds.
  • Criação de usuário, através de um endpoint.
  • Login na aplicação.
  • Listando usuários, somente com token JWT.

Screenshot - preview

Obs: Já foi implementada a cryptografia da senha. :p

Habilidades trabalhadas:

  • Criar uma aplicação usando o Docker para o desenvolvimento.

  • Montar uma API e um banco de dados, seguindo boas práticas de desenvolvimento de software!

  • Criar e associar tabelas usando um ORM.

  • Criar endpoints para manipular as informações de um banco de dados MySQL.

  • Autenticar pessoas usuárias através do JWT.

  • Colocar sob nova ótica o jeito programar. Foi necessário, a todo momento, pensar “será que essa é a melhor forma de fazer isso?” ;)

O que foi avaliado?

  • Aderência do código à especificação. O programa deve se comportar como especificado.

  • Respeitar os princípios de uma API REST

  • Com a ajuda do Sequelize, criar as relaçẽos entre as entidades, seguindo um modelo DER.

  • Ter uma autenticação em JWT.

  • Organização do código.


Rodando no Docker vs Localmente

👉 Com Docker

⚠️ Antes de começar, seu docker-compose precisa estar na versão 1.29 ou superior. Veja aqui ou na documentação como instalá-lo.

ℹ️ Rode os serviços do node e o db com o comando docker-compose up -d.

  • Lembre-se o mysql tem que estar rodadno localmente na porta padrão (3306).

  • Esses serviços irão inicializar um container chamado blogs_api e outro chamado blogs_api_db;

  • A partir daqui você pode rodar o container node blogs_api via CLI:

ℹ️ Use o comando docker exec -it blogs_api bash.

  • Ele te dará acesso ao terminal interativo do container criado pelo compose, que está rodando em segundo plano.

ℹ️ Instale as dependências com npm install. (Instale dentro do container)

  • ⚠️ Atenção: Caso opte por utilizar o Docker, TODOS os comandos disponíveis no package.json (npm run dev, npm run db:reset, npm run db:seed:all, ...) devem ser executados DENTRO do container.

ℹ️ Crie o DB e rode as migrations com npm run db:reset

ℹ️ Popule o DB com npm run db:seed:all

ℹ️ Rode a aplicação em modo dev(nodemon) com npm run dev


👉 Sem Docker

  • ⚠️ Atenção: Você vai precisar ter o MySQL instalado e rodando no seu PC.

ℹ️ Instale as dependências com npm install

ℹ️ Crie o DB e rode as migrations com npm run db:reset

ℹ️ Popule o DB com npm run db:seed:all

ℹ️ Rode a aplicação em modo dev(nodemon) com npm run dev

  • ✨ Dica: Para rodar o projeto desta forma, obrigatoriamente você deve ter o node instalado em seu computador.

Obs: Se você optar em usar a extenção do VSCode Thunder Client, use o arquivo thunder-collection_Blogs_api.json para importar a collection Blog Api



🎲 Diagrama:

Diagrama de Entidade-Relacionamento

A construção das tabelas foi orientada pelo DER a seguir usando o Sequelize ORM:

DER

Saiba mais:

Modelo de Banco de Dados Relacional para Sistema de Blog

O esquema apresentado na imagem descreve as tabelas de um banco de dados e suas relações em um sistema de blog.

Tabelas e Campos

Categorias (categories)

  • id INT: Chave primária que identifica cada categoria de forma única.
  • name VARCHAR(255): Nome da categoria.

Posts de Categorias (posts_categories)

  • post_id INT: Chave estrangeira que referencia o id dos blog_posts.
  • category_id INT: Chave estrangeira que referencia o id das categories.

Posts do Blog (blog_posts)

  • id INT: Chave primária que identifica cada post do blog.
  • title VARCHAR(255): Título do post.
  • content VARCHAR(255): Conteúdo do post.
  • user_id INT: Chave estrangeira que referencia o id do autor na tabela users.
  • published DATETIME: Data e hora de publicação do post.
  • updated DATETIME: Data e hora da última atualização do post.

Usuários (users)

  • id INT: Chave primária que identifica cada usuário.
  • display_name VARCHAR(255): Nome que é exibido no blog.
  • email VARCHAR(255): E-mail do usuário.
  • password VARCHAR(255): Senha do usuário.
  • image VARCHAR(255): Caminho ou link para a imagem de perfil do usuário.

Relacionamentos

  • Um-para-Muitos: Entre users e blog_posts, indicando que um usuário pode escrever vários posts.
  • Muitos-para-Muitos: Entre blog_posts e categories, facilitado pela tabela posts_categories, significando que um post pode ter várias categorias e uma categoria pode incluir vários posts.


Blog API - Documentação de Endpoints

Saiba mais:

Healthy:

GET /status

👉 Método: GET /status

  • Objetivo: Uma rota /status é comumente usada para verificar a saúde de uma API. Quando os clientes acessam essa rota, o servidor pode responder com informações sobre o status do serviço, se ele está funcionando corretamente ou se há algum problema. É uma maneira útil de monitorar a integridade da sua API.

  • Código HTTP (sucesso): 200 - OK

  • Output (exemplo):

    {
      "message": "[Healthy] - API on!!!"
    }

Authentication:

GET /login

👉 Método: GET /login

Cenários de Exceção:
  1. 400 - BAD_REQUEST -> se o campo 'email' ou 'password' forem inválidos ou não cadastrados
  2. 401 - UNAUTHORIZED -> se o campo 'email' não estiver cadastrado
  3. 404 - NOT_FOUND -> se a rota na API não existir
  4. 500 - INTERNAL_SERVER_ERROR -> se der outro erro

Cenário de Sucesso:

  • Objetivo: Autenticar um usuário, já criado anteriormente, na aplicação

  • Código HTTP (sucesso): 200 - OK

  • Output (exemplo):

      {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkIjp7InVzZXJJZCI6MywibmFtZSI6Ik1hcmNlbG8gQXRhw61kZSIsImF2YXRhciI6Imh0dHA6Ly90ZXN0ZSJ9LCJpYXQiOjE3MDI5ODgyOTgsImV4cCI6MTcwMjk4OTE5OH0.CrIXONvWKLD98NndrLSDmEQNmuoO1zM_2ur6msF17hY"
      }

Users:

GET /user

👉 Método: GET /user

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  2. 404 - NOT_FOUND -> se a rota na API não existir
  3. 500 - INTERNAL_SERVER_ERROR -> se der erro ao tentar listar os usuários

Cenário de Sucesso:

  • Objetivo: Listar todos os usuários da aplicação

  • Código HTTP (sucesso): 200 - OK

  • Output (exemplo):

    [
      {
        "id": 1,
        "displayName": "Lewis Hamilton",
        "email": "lewishamilton@gmail.com",
        "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg"
      },
      {
        "id": 2,
        "displayName": "Michael Schumacher",
        "email": "MichaelSchumacher@gmail.com",
        "image": "https://sportbuzz.uol.com.br/media/_versions/gettyimages-52491565_widelg.jpg"
      }
    ]

GET /user/{id}

👉 Método: GET /user/{id}

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  2. 404 - NOT_FOUND -> se o usuário com o ID especificado não for encontrado
  3. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro ao tentar recuperar o usuário.

Cenário de Sucesso:

  • Objetivo: Recuperar informações de um usuário específico através do seu ID.

  • Código HTTP (sucesso): 200 - OK

  • Output (exemplo):

      {
        "id": 1,
        "displayName": "Lewis Hamilton",
        "email": "lewishamilton@gmail.com",
        "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg"
      }

POST /user

👉 Método: POST /user

Cenários de Exceção:
  1. 400 - BAD_REQUEST -> se os campos forem inválidos
  2. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  3. 404 - NOT_FOUND -> se a rota na API não existir
  4. 409 - CONFLICT -> se o usuário já existir
  5. 500 - INTERNAL_SERVER_ERROR -> se der erro ao tentar listar os usuários

Cenário de Sucesso:

  • Objetivo: Listar todos os usuários da aplicação

  • Código HTTP (sucesso): 200 - OK

  • Output (exemplo):

      [
        {
          "id": 1,
          "displayName": "Lewis Hamilton",
          "email": "lewishamilton@gmail.com",
          "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg"
        },
        {
          "id": 2,
          "displayName": "Michael Schumacher",
          "email": "MichaelSchumacher@gmail.com",
          "image": "https://sportbuzz.uol.com.br/media/_versions/gettyimages-52491565_widelg.jpg"
        }
      ]

DELETE /user

👉 Método: DELETE /user/me

Cenários de Exceção:
  1. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro ao tentar deletar o post

Cenário de Sucesso:

  • Objetivo: Permitir que o usuário logado delete sua própria conta.

  • Código HTTP (sucesso): 204 - No Content

  • Nota: Não há output para este método, pois a resposta bem-sucedida é um status HTTP 204 - NO_CONTENT.


Categories:

GET /categories

👉 Método: GET /categories

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  2. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro ao tentar listar as categorias

Cenário de Sucesso:

  • Objetivo: Listar todas as categorias de posts disponíveis na aplicação.

  • Código HTTP (sucesso): 200 - OK

  • Output (exemplo):

      [
        {
          "id": 1,
          "name": "Inovação"
        },
        {
          "id": 2,
          "name": "Escola"
        }
      ]

POST /categories

👉 Método: POST /categories

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  2. 400 - BAD_REQUEST -> se o campo 'name' não for fornecido ou for menor que 5 caracteres
  3. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro ao tentar criar a categoria

Cenário de Sucesso:

  • Objetivo: Criar uma nova categoria de posts.

  • Código HTTP (sucesso): 201 - CREATED

  • Input (exemplo):

      {
        "name": "Nova categoria"
      }

Post:

GET /post

👉 Método: GET /post

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  2. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro ao tentar listar os posts.

Cenário de Sucesso:

  • Objetivo: Listar todos os posts criados, incluindo informações sobre suas categorias e o usuário que os criou.

  • Código HTTP (sucesso): 200 - OK

  • Output (exemplo):

    [
      {
        "id": 1,
        "title": "Post do Ano",
        "content": "Melhor post do ano",
        "userId": 1,
        "published": "2022-08-01T19:58:00.000Z",
        "updated": "2022-08-01T19:58:51.000Z",
        "user": {
          "id": 1,
          "displayName": "Lewis Hamilton",
          "email": "lewishamilton@gmail.com",
          "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg"
        },
        "categories": [
          {
            "id": 1,
            "name": "Inovação",
            "PostCategory": {
              "postId": 1,
              "categoryId": 1
            }
          }
        ]
      },
      {
        "id": 2,
        "title": "Vamos que vamos",
        "content": "Foguete não tem ré",
        "userId": 1,
        "published": "2022-08-01T19:58:00.000Z",
        "updated": "2022-08-01T19:58:51.000Z",
        "user": {
          "id": 1,
          "displayName": "Lewis Hamilton",
          "email": "lewishamilton@gmail.com",
          "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg"
        },
        "categories": [
          {
            "id": 2,
            "name": "Escola",
            "PostCategory": {
              "postId": 2,
              "categoryId": 2
            }
          }
        ]
      }
    ]

GET /post/search

👉 Método: GET /post/search

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  2. 404 - NOT_FOUND -> se nenhum post corresponder ao termo de pesquisa
  3. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro durante a busca.

Cenário de Sucesso:

  • Objetivo: Listar todos os posts que contenham em seu título ou conteúdo a palavra pesquisada.

  • Código HTTP (sucesso): 200 - OK

  • Exemplo de uso da query:

    • URL: http://localhost:3000/post/search?q=foguete
  • Output (exemplo):

    [
      {
        "id": 2,
        "title": "Vamos que vamos",
        "content": "Foguete não tem ré",
        "userId": 1,
        "published": "2022-08-01T19:58:00.000Z",
        "updated": "2022-08-01T19:58:51.000Z",
        "user": {
          "id": 1,
          "displayName": "Lewis Hamilton",
          "email": "lewishamilton@gmail.com",
          "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg"
        },
        "categories": [
          {
            "id": 2,
            "name": "Escola",
            "PostCategory": {
              "postId": 2,
              "categoryId": 2
            }
          }
        ]
      }
    ]

GET /post/{id}

👉 Método: GET /post/{id}

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  2. 404 - NOT_FOUND -> se o post com o ID especificado não for encontrado
  3. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro ao tentar recuperar o post

Cenário de Sucesso:

  • Objetivo: Recuperar informações de um post específico através do seu ID.

  • Código HTTP (sucesso): 200 - OK

  • Output (exemplo):

    {
      "id": 1,
      "title": "Post do Ano",
      "content": "Melhor post do ano",
      "userId": 1,
      "published": "2022-08-01T19:58:00.000Z",
      "updated": "2022-08-01T19:58:51.000Z",
      "user": {
        "id": 1,
        "displayName": "Lewis Hamilton",
        "email": "lewishamilton@gmail.com",
        "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg"
      },
      "categories": [
        {
          "id": 1,
          "name": "Inovação",
          "PostCategory": {
            "postId": 1,
            "categoryId": 1
          }
        }
      ]
    }

POST /post

👉 Método: POST /post

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  2. 400 - BAD_REQUEST -> se algum dos campos obrigatórios não for fornecido ou não atender aos requisitos mínimos
  3. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro ao tentar criar o post

Cenário de Sucesso:

  • Objetivo: Criar um novo post.

  • Código HTTP (sucesso): 201 - CREATED

  • Input (exemplo):

    {
      "title": "Título um",
      "content": "conteúdo um",
      "categoryIds": [2, 3]
    }
  • Output (exemplo):

    {
      "id": 3,
      "title": "Título um",
      "content": "conteúdo um",
      "userId": 3,
      "updated": "2023-12-20T12:07:57.044Z",
      "published": "2023-12-20T12:07:57.044Z"
    }

PUT /post/{id}

👉 Método: PUT /post/{id}

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se não encontrar o token ou for inválido ou expirado.
  2. 401 - UNAUTHORIZED -> se o post não for do usuário que esta logado.
  3. 404 - NOT_FOUND -> se o post com o ID especificado não for encontrado
  4. 400 - BAD_REQUEST -> se algum dos campos obrigatórios não for fornecido ou não atender aos requisitos mínimos
  5. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro ao tentar atualizar o post

Cenário de Sucesso:

  • Objetivo: Atualizar um post específico através do seu ID, somente se o usuário for o dono do post.

  • Código HTTP (sucesso): 200 - OK

  • Input (exemplo):

    {
      "title": "Título Novo 2",
      "content": "Conteúdo novo 2"
    }
  • Output (exemplo):

      {
        "id": 3,
        "title": "Título Novo 2",
        "content": "Conteúdo novo 2",
        "userId": 3,
        "published": "2023-12-20T12:07:57.000Z",
        "updated": "2023-12-20T12:07:57.000Z",
        "user": {
          "id": 3,
          "displayName": "Marcelo Ataíde",
          "email": "marcelo@email.com",
          "image": "http://teste"
        },
        "categories": [
          {
            "id": 2,
            "name": "Escola",
            "PostCategory": {
              "postId": 3,
              "categoryId": 2
            }
          },
          {
            "id": 3,
            "name": "Nova categoria",
            "PostCategory": {
              "postId": 3,
              "categoryId": 3
            }
          }
        ]
      }

DELETE /post/{id}

👉 Método: DELETE /post/{id}

Cenários de Exceção:
  1. 401 - UNAUTHORIZED -> se o usuário tentando deletar o post não for o dono dele
  2. 404 - NOT_FOUND -> se o post com o ID especificado não for encontrado
  3. 500 - INTERNAL_SERVER_ERROR -> se ocorrer um erro ao tentar deletar o post

Cenário de Sucesso:

  • Objetivo: Deletar um post específico, sendo permitido apenas se o usuário for o dono do post.

  • Código HTTP (sucesso): 204 - NO_CONTENT

  • Nota: Não há output para este método, pois a resposta bem-sucedida é um status HTTP 204 - NO_CONTENT.

Próximos passos:

  • Criptografar a senha antes de salvar o usuário no banco. ✅
  • Fazer os teste de unidade e de integração.