https://github.com/jsericksk/quotes-api
Quotes API using TypeScript, NodeJS and Express.
https://github.com/jsericksk/quotes-api
express jest-test nodejs postgresql supertest typescript zod-validators
Last synced: 15 days ago
JSON representation
Quotes API using TypeScript, NodeJS and Express.
- Host: GitHub
- URL: https://github.com/jsericksk/quotes-api
- Owner: jsericksk
- Created: 2023-06-09T13:28:12.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2023-07-10T19:41:11.000Z (almost 3 years ago)
- Last Synced: 2025-03-27T12:35:13.701Z (about 1 year ago)
- Topics: express, jest-test, nodejs, postgresql, supertest, typescript, zod-validators
- Language: TypeScript
- Homepage:
- Size: 419 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
Quotes API
## Objetivo
Esse é um projeto básico criado com o objetivo de aprender mais sobre APIs/backend, assim como **TypeScript** e **NodeJS**. É um projeto com fins unicamente de estudos.
## :pencil2: Funcionalidades
A API tem como principal função publicar e obter frases, sendo necessário se registrar para tal. Possui um CRUD completo de frases para usuários logados, com opções de adicionar, obter, atualizar e excluir frases, além de contar com o recurso de paginação ao obter as frases. Os dados são armazenados em um banco de dados **PostegreSQL**.
## :hammer_and_wrench: Principais tecnologias e bibliotecas utilizadas
- [NodeJS](https://nodejs.org/en) e [Express](https://expressjs.com);
- [Knex](https://github.com/knex/knex): Query builder;
- [PostgreSQL](https://www.postgresql.org): Banco de dados;
- [Jest](https://github.com/jestjs/jest) e [SuperTest](https://github.com/ladjs/supertest): Testes de integração;
- [Bcrypt.js](https://github.com/dcodeIO/bcrypt.js): Criptografia de senhas;
- [Zod](https://github.com/colinhacks/zod): Validação de requisições;
- [Http-Status-Codes](https://github.com/prettymuchbryce/http-status-codes): Status codes mais legíveis.
## :rocket: Executar localmente
Estou utilizando o **yarn**, mas você pode utilizar o gerenciador de pacotes de sua preferência. Antes de executar localmente, é necessário definir as variáveis de ambiente no arquivo **.env**. Você precisa criar o arquivo .env e copiar as variáveis do **.env.example**, preenchendo com os valores de configuração adequados.
**IMPORTANTE**: *ACCESS_TOKEN_SECRET_KEY* e *REFRESH_TOKEN_SECRET_KEY* devem ter valores diferentes, caso contrário, um refresh token poderá ser usado como access token.
- **yarn dev**: Executar localmente.
- **yarn test** ou **yarn jest**: Rodar todos os testes.
- **yarn knex:migrate:** Executar migrations.
- **yarn knex:rollback:** Executar rollback.
- **yarn knex:seed:** Executar seeds.
## :memo: API Docs
Rotas **/auth**:
- [Registrar usuário](#registrar-usuário)
- [Login](#login)
- [Refresh token](#refresh-token)
Rotas **/quotes**:
- [Obter todas as frases](#obter-todas-as-frases)
- [Obter frase por id](#obter-frase-por-id)
- [Publicar frase](#publicar-frase)
- [Atualizar frase](#atualizar-frase)
- [Excluir frase](#excluir-frase)
Manipulação de erros:
- [Error handling](#error-handling)
### Registrar usuário
> POST /auth/register
- **Request - Body:**
```json
{
"email": "Email do usuário",
"username": "Nome do usuário. Deve conter no mínimo 3 caracteres e no máximo 50",
"password": "Senha do usuário. Deve conter no mínimo 6 caracteres"
}
```
- **Response - Status 201:** ID do usuário registrado (int).
### Login
> POST /auth/login
- **Request - Body:**
```json
{
"email": "Email do usuário",
"password": "Senha do usuário. Deve conter no mínimo 6 caracteres"
}
```
- **Response - Status 200:**
```json
{
"accessToken": "Access token válido por 1h",
"refreshToken": "Refresh token válido por 7 dias (ou até ser utilizado para gerar um novo access token)"
}
```
### Refresh token
> POST /auth/refresh-token
- **Request - Body:**
```json
{
"refreshToken": "Refresh token válido no formato JWT gerado durante o login"
}
```
- **Response - Status 200:**
```json
{
"accessToken": "Novo access token válido por 1h",
"refreshToken": "Novo refresh token válido por 7 dias (ou até ser utilizado para gerar um novo access token)"
}
```
### Obter todas as frases
**NOTA IMPORTANTE:** Todas as rotas de frases são protegidas e precisam de um Authorization Header.
Passe **Authorization: Bearer ***access token***** no header de requisição.
- Você pode pesquisar por frases ao fornecer um **filter** e obter todas as frases de um determinado usuário ao fornecer o **userId**.
> GET /quotes
> GET /quotes?page=1
> GET /quotes?filter=conhecimento
> GET /quotes?userId=1
> GET /quotes?page=1&filter=conhecimento
> GET /quotes?userId=1&filter=conhecimento
- **Response - Status 200:**
```json
{
"info": {
"count": 20,
"pages": 2,
"next": "/quotes?page=2",
"previous": null
},
"results": [
{
"id": 1,
"quote": "A imaginação é mais importante que o conhecimento.",
"author": "Albert Einstein",
"postedByUsername": "John",
"postedByUserId": 1,
"publicationDate": "2023-06-21T15:20:28.936Z"
},
[...]
]
}
```
- **count**: Número total de frases.
- **pages**: Número total de páginas disponíveis.
- **next**: Próxima página. null se não houver nenhuma.
- **previous**: Página anterior. null se não houver nenhuma.
- **results**: Frases encontradas.
- O limite de frases por página é 15.
### Obter frase por id
> GET /quotes/:id
- **Response - Status 200:**
```json
{
"id": 1,
"quote": "A imaginação é mais importante que o conhecimento.",
"author": "Albert Einstein",
"postedByUsername": "John",
"postedByUserId": 1,
"publicationDate": "2023-06-21T15:20:28.936Z"
}
```
### Publicar frase
> POST /quotes
- **Request - Body:**
```json
{
"quote": "Frase. Deve conter no mínimo 7 caracteres e no máximo 1000",
"author": "Nome do autor. Deve conter no mínimo 1 caractere e no máximo 80"
}
```
- **Response - Status 201:**
```json
{
"id": 1,
"quote": "A imaginação é mais importante que o conhecimento.",
"author": "Albert Einstein",
"postedByUsername": "John",
"postedByUserId": 1,
"publicationDate": "2023-06-21T15:20:28.936Z"
}
```
### Atualizar frase
> PUT /quotes/:id
- **Request - Body:**
```json
{
"quote": "Frase atualizada. Deve conter no mínimo 7 caracteres e no máximo 1000",
"author": "Nome do autor atualizado. Deve conter no mínimo 1 caractere e no máximo 80"
}
```
- **Response - Status 204:** Não há body de resposta.
### Excluir frase
> DELETE /quotes/:id
- **Response - Status 204:** Não há body de resposta.
### Error handling
Todos os endpoints que precisam de um **body/params/query** podem retornar uma **BAD_REQUEST (400)** se a requisição contiver valores inválidos. Exemplos de erros:
- Login:
```json
{
"errors": {
"body": {
"email": "Invalid email",
"password": "String must contain at least 6 character(s)"
}
}
}
```
- Tentar obter todas as frases com **?page=abc**:
```json
{
"errors": {
"query": {
"page": "Expected number, received nan"
}
}
}
```
- Atualizar frase com campos no body e id inválidos:
```json
{
"errors": {
"body": {
"quote": "String must contain at least 7 character(s)",
"author": "String must contain at least 1 character(s)"
},
"params": {
"id": "Expected number, received nan"
}
}
}
```
Todos os endpoints podem retornar um body contendo um **error** e **error_code** padrão. **error_code** sempre será retornado com um valor "unespecifed" caso não seja importante distinguir o tipo de erro. Códigos de erro disponíveis:
- **email_already_exists:** Quando tentar se registrar com e-mail que já existe.
- **username_already_exists:** Quando tentar se registrar com nome de usuário que já existe.
- **search_without_results:** Quando nenhuma frase com o "filter" aplicado for encontrada.
- **user_without_posts:** Quando o usuário não possuir nenhuma frase publicada.
- **invalid_page:** Quando uma página inválida for informada na paginação.
```json
{
"error": "Email not available",
"error_code": "email_not_available"
}
```
Alguns endpoints podem retornar **NOT_FOUND (404)** se o recurso não for encontrado ou a frase não pertencer ao usuário.
- Tentar atualizar ou excluir com o id de uma frase que não existe ou não pertence ao usuário:
```json
{
"error": "There is no quote with the given id",
"error_code": "unspecified"
}
```
- Tentar obter frases com uma **page** maior do que a quantidade de páginas disponíveis:
```json
{
"error": "Invalid page, no more items",
"error_code": "invalid_page"
}
```