Construção de API

Back-end Web

Rotas do Host API

MétodoCaminhoResposta
POST/hostsCria um novo host
GET/hostsRetorna todos os hosts
GET/hosts?id=1Retorna o host com id igual a 1
GET/hosts/1Retorna o host com id igual a 1
PUT/hosts/1Atualiza o host com id igual a 1
DELETE/hosts/1Exclui o host com id igual a 1

HTTP Status Codes

HTTP Status Codes

Exemplos de Códigos

CódigoNomeSignificado
200OkSolicitação gerada com sucesso
201CreatedSolicitação gerada com sucesso e um novo recurso foi criado como resultado
204No ContentSolicitação gerada com sucesso e não há conteúdo para ser enviado
400Bad RequestSolicitação não compreendida por motivos de erro
401UnauthorizedSolicitação foi bloqueada porque não possui credenciais de autenticação válidas
404Not FoundO servidor não pode encontrar o recurso solicitado
500Internal Server ErrorO servidor encontrou uma situação com a qual não sabe lidar.

API de Investimentos

Open in GitHub

Arquivos
back
├── package-lock.json
├── package.json
├── requests.http
└── src
    ├── data
    │   └── hosts.js
    ├── index.js
    ├── routes.js
    └── routes.test.js
Arquivos
back
├── package-lock.json
├── package.json
├── requests.http
└── src
    ├── data
    │   └── hosts.js
    ├── index.js
    ├── routes.js
    └── routes.test.js
/codes/expressjs/monitor-app-api/back/src/data/hosts.js
export const hosts = [
  {
    id: 'e4cfb6bb-4431-42a9-b660-d5701b2f49cd',
    name: 'Google DNS',
    address: '8.8.8.8',
  },
  {
    id: 'a2bb615a-6153-41bf-8cbe-0bfb538ce511',
    name: 'Google Search',
    address: 'www.google.com',
  },
];
 
/codes/expressjs/monitor-app-api/back/src/data/hosts.js
export const hosts = [
  {
    id: 'e4cfb6bb-4431-42a9-b660-d5701b2f49cd',
    name: 'Google DNS',
    address: '8.8.8.8',
  },
  {
    id: 'a2bb615a-6153-41bf-8cbe-0bfb538ce511',
    name: 'Google Search',
    address: 'www.google.com',
  },
];
 
/codes/expressjs/monitor-app-api/back/src/index.js
import 'express-async-errors';
import express from 'express';
import cors from 'cors';
import morgan from 'morgan';
import router from './routes.js';
 
const server = express();
 
server.use(morgan('tiny'));
 
server.use(
  cors({
    origin: '*',
    methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
    preflightContinue: false,
  })
);
 
server.use(express.json());
 
server.use(router);
 
server.listen(3000, () => {
  console.log('Server is running on port 3000');
});
 
export default server;
 
/codes/expressjs/monitor-app-api/back/src/index.js
import 'express-async-errors';
import express from 'express';
import cors from 'cors';
import morgan from 'morgan';
import router from './routes.js';
 
const server = express();
 
server.use(morgan('tiny'));
 
server.use(
  cors({
    origin: '*',
    methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
    preflightContinue: false,
  })
);
 
server.use(express.json());
 
server.use(router);
 
server.listen(3000, () => {
  console.log('Server is running on port 3000');
});
 
export default server;
 
/codes/expressjs/monitor-app-api/back/src/routes.js
import express from 'express';
import { v4 as uuidv4 } from 'uuid';
import { hosts } from './data/hosts.js';
 
class HttpError extends Error {
  constructor(message, code = 400) {
    super(message);
    this.code = code;
  }
}
 
const router = express.Router();
 
router.post('/hosts', (req, res) => {
  const { name, address } = req.body;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  const id = uuidv4();
 
  const newHost = { id, name, address };
 
  hosts.push(newHost);
 
  res.status(201).json(newHost);
});
 
router.get('/hosts', (req, res) => {
  const where = req.query;
 
  if (where) {
    const field = Object.keys(where)[0];
 
    const value = where[field];
 
    const filteredHosts = hosts.filter((host) =>
      host[field] instanceof String
        ? host[field].toLowerCase().includes(value.toLowerCase())
        : host[field] === value
    );
 
    return res.json(filteredHosts);
  }
 
  return res.json(hosts);
});
 
router.get('/hosts/:id', (req, res) => {
  const { id } = req.params;
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to read a host');
  }
 
  return res.json(hosts[index]);
});
 
router.put('/hosts/:id', (req, res) => {
  const { name, address } = req.body;
 
  const { id } = req.params;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  const newHost = { id, name, address };
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to update a host');
  }
 
  hosts[index] = newHost;
 
  return res.json(newHost);
});
 
router.delete('/hosts/:id', (req, res) => {
  const { id } = req.params;
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to delete a host');
  }
 
  hosts.splice(index, 1);
 
  return res.send(204);
});
 
// 404 handler
router.use((req, res, next) => {
  return res.status(404).json({ message: 'Content not found!' });
});
 
// Error handler
router.use((err, req, res, next) => {
  // console.error(err.message);
  console.error(err.stack);
 
  if (err instanceof HttpError) {
    return res.status(err.code).json({ message: err.message });
  }
 
  // next(err);
  return res.status(500).json({ message: 'Something broke!' });
});
 
export default router;
 
/codes/expressjs/monitor-app-api/back/src/routes.js
import express from 'express';
import { v4 as uuidv4 } from 'uuid';
import { hosts } from './data/hosts.js';
 
class HttpError extends Error {
  constructor(message, code = 400) {
    super(message);
    this.code = code;
  }
}
 
const router = express.Router();
 
router.post('/hosts', (req, res) => {
  const { name, address } = req.body;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  const id = uuidv4();
 
  const newHost = { id, name, address };
 
  hosts.push(newHost);
 
  res.status(201).json(newHost);
});
 
router.get('/hosts', (req, res) => {
  const where = req.query;
 
  if (where) {
    const field = Object.keys(where)[0];
 
    const value = where[field];
 
    const filteredHosts = hosts.filter((host) =>
      host[field] instanceof String
        ? host[field].toLowerCase().includes(value.toLowerCase())
        : host[field] === value
    );
 
    return res.json(filteredHosts);
  }
 
  return res.json(hosts);
});
 
router.get('/hosts/:id', (req, res) => {
  const { id } = req.params;
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to read a host');
  }
 
  return res.json(hosts[index]);
});
 
router.put('/hosts/:id', (req, res) => {
  const { name, address } = req.body;
 
  const { id } = req.params;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  const newHost = { id, name, address };
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to update a host');
  }
 
  hosts[index] = newHost;
 
  return res.json(newHost);
});
 
router.delete('/hosts/:id', (req, res) => {
  const { id } = req.params;
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to delete a host');
  }
 
  hosts.splice(index, 1);
 
  return res.send(204);
});
 
// 404 handler
router.use((req, res, next) => {
  return res.status(404).json({ message: 'Content not found!' });
});
 
// Error handler
router.use((err, req, res, next) => {
  // console.error(err.message);
  console.error(err.stack);
 
  if (err instanceof HttpError) {
    return res.status(err.code).json({ message: err.message });
  }
 
  // next(err);
  return res.status(500).json({ message: 'Something broke!' });
});
 
export default router;
 
/codes/expressjs/monitor-app-api/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
 
### Create a host
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server",
  "address": "1.1.1.1"
}
 
### Create a host without name or address
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server"
}
 
### Read hosts
GET {{server}}/hosts
 
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
# GET {{server}}/hosts?name=DNS
# GET {{server}}/hosts?name=dns
 
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
 
### Read a host by id with invalid id
GET {{server}}/hosts/x
 
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}
 
### Update a host without name or address
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS"
}
 
### Update a host with invalid id
PUT {{server}}/hosts/x
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}
 
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
 
### Delete a host with invalid id
DELETE {{server}}/hosts/x
 
/codes/expressjs/monitor-app-api/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
 
### Create a host
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server",
  "address": "1.1.1.1"
}
 
### Create a host without name or address
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server"
}
 
### Read hosts
GET {{server}}/hosts
 
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
# GET {{server}}/hosts?name=DNS
# GET {{server}}/hosts?name=dns
 
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
 
### Read a host by id with invalid id
GET {{server}}/hosts/x
 
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}
 
### Update a host without name or address
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS"
}
 
### Update a host with invalid id
PUT {{server}}/hosts/x
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}
 
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
 
### Delete a host with invalid id
DELETE {{server}}/hosts/x
 
/codes/expressjs/monitor-app-api/back/package.json
{
  "name": "invest-app",
  "type": "module",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "test": "node --experimental-vm-modules ./node_modules/.bin/jest src",
    "test:coverage": "node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
  },
  "jest": {
    "collectCoverage": true,
    "testTimeout": 20000,
    "coverageReporters": [
      "json",
      "html"
    ]
  },
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "express-async-errors": "^3.1.1",
    "morgan": "^1.10.0",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "supertest": "^6.3.4"
  }
}
 
/codes/expressjs/monitor-app-api/back/package.json
{
  "name": "invest-app",
  "type": "module",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "test": "node --experimental-vm-modules ./node_modules/.bin/jest src",
    "test:coverage": "node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
  },
  "jest": {
    "collectCoverage": true,
    "testTimeout": 20000,
    "coverageReporters": [
      "json",
      "html"
    ]
  },
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "express-async-errors": "^3.1.1",
    "morgan": "^1.10.0",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "supertest": "^6.3.4"
  }
}
 

Create

/codes/expressjs/invest-app-api/src/routes.js
router.post('/hosts', (req, res) => {
  const { name, address } = req.body;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  const id = uuidv4();
 
  const newHost = { id, name, address };
 
  hosts.push(newHost);
 
  res.status(201).json(newHost);
});
/codes/expressjs/invest-app-api/src/routes.js
router.post('/hosts', (req, res) => {
  const { name, address } = req.body;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  const id = uuidv4();
 
  const newHost = { id, name, address };
 
  hosts.push(newHost);
 
  res.status(201).json(newHost);
});
/codes/expressjs/invest-app-api/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
 
### Create a host
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server",
  "address": "1.1.1.1"
}
 
### Create a host (bad params)
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server"
}
/codes/expressjs/invest-app-api/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
 
### Create a host
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server",
  "address": "1.1.1.1"
}
 
### Create a host (bad params)
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server"
}

Read

/codes/expressjs/invest-app-api/src/routes.js
router.get('/hosts', (req, res) => {
  const where = req.query;
 
  if (where) {
    const field = Object.keys(where)[0];
 
    const value = where[field];
 
    const filteredHosts = hosts.filter((host) =>
      host[field] instanceof String
        ? host[field].toLowerCase().includes(value.toLowerCase())
        : host[field] === value
    );
 
    return res.json(filteredHosts);
  }
 
  return res.json(hosts);
});
 
router.get('/hosts/:id', (req, res) => {
  const { id } = req.params;
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to read a host');
  }
 
  return res.json(hosts[index]);
});
/codes/expressjs/invest-app-api/src/routes.js
router.get('/hosts', (req, res) => {
  const where = req.query;
 
  if (where) {
    const field = Object.keys(where)[0];
 
    const value = where[field];
 
    const filteredHosts = hosts.filter((host) =>
      host[field] instanceof String
        ? host[field].toLowerCase().includes(value.toLowerCase())
        : host[field] === value
    );
 
    return res.json(filteredHosts);
  }
 
  return res.json(hosts);
});
 
router.get('/hosts/:id', (req, res) => {
  const { id } = req.params;
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to read a host');
  }
 
  return res.json(hosts[index]);
});
/codes/expressjs/invest-app-api/back/requests.http
@server=http://localhost:3000
 
### Read hosts
GET {{server}}/hosts
 
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
 
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
 
### Read a host by id (bad params)
GET {{server}}/hosts/x
/codes/expressjs/invest-app-api/back/requests.http
@server=http://localhost:3000
 
### Read hosts
GET {{server}}/hosts
 
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
 
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
 
### Read a host by id (bad params)
GET {{server}}/hosts/x

Update

/codes/expressjs/invest-app-api/src/routes.js
router.put('/hosts/:id', (req, res) => {
  const { name, address } = req.body;
 
  const { id } = req.params;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  const newHost = { id, name, address };
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to update a host');
  }
 
  hosts[index] = newHost;
 
  return res.json(newHost);
});
/codes/expressjs/invest-app-api/src/routes.js
router.put('/hosts/:id', (req, res) => {
  const { name, address } = req.body;
 
  const { id } = req.params;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  const newHost = { id, name, address };
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to update a host');
  }
 
  hosts[index] = newHost;
 
  return res.json(newHost);
});
@createdHostId = {{createHost.response.body.$.id}}
 
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}
 
### Update a host (bad params)
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS"
}
 
### Update a host (bad params)
PUT {{server}}/hosts/x
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}
@createdHostId = {{createHost.response.body.$.id}}
 
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}
 
### Update a host (bad params)
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS"
}
 
### Update a host (bad params)
PUT {{server}}/hosts/x
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}

Delete

/codes/expressjs/invest-app-api/src/routes.js
router.delete('/hosts/:id', (req, res) => {
  const { id } = req.params;
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to delete a host');
  }
 
  hosts.splice(index, 1);
 
  return res.send(204);
});
/codes/expressjs/invest-app-api/src/routes.js
router.delete('/hosts/:id', (req, res) => {
  const { id } = req.params;
 
  const index = hosts.findIndex((host) => host.id === id);
 
  if (!hosts[index]) {
    throw new HttpError('Unable to delete a host');
  }
 
  hosts.splice(index, 1);
 
  return res.send(204);
});
/codes/expressjs/invest-app-api/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
 
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
 
### Delete a host (bad params)
DELETE {{server}}/hosts/x
/codes/expressjs/invest-app-api/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
 
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
 
### Delete a host (bad params)
DELETE {{server}}/hosts/x

Teste

Jest, Supertest:

$ npm i jest supertest -D
 
$ npm run test
$ npm i jest supertest -D
 
$ npm run test
/codes/expressjs/monitor-app-api/back/src/routes.test.js
import request from 'supertest';
import app from './index.js';
 
let createdHost;
 
const newHost = {
  name: 'DNS Server',
  address: '1.1.1.1',
};
 
const updatedHost = {
  name: 'Cloudflare DNS',
  address: '1.1.1.1',
};
 
describe('Moniotr App', () => {
  describe('Hosts Endpoints', () => {
    describe('POST /hosts', () => {
      it('should create a new host', async () => {
        const response = await request(app).post('/hosts').send(newHost);
 
        createdHost = response.body;
 
        expect(response.statusCode).toBe(201);
      });
 
      it('should not create a new host without name or address', async () => {
        const response = await request(app).post('/hosts').send({
          name: 'DNS Server',
        });
 
        expect(response.statusCode).toBe(400);
      });
    });
 
    describe('GET /hosts', () => {
      it('should show all hosts', async () => {
        const response = await request(app).get('/hosts');
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should list the valid host', async () => {
        const response = await request(app).get('/hosts');
 
        const hasValidHost = response.body.some(
          (host) => host.address === createdHost.address
        );
 
        expect(hasValidHost).toBeTruthy();
      });
 
      it('should show all hosts by name', async () => {
        const response = await request(app).get('/hosts?name=DNS');
 
        expect(response.statusCode).toBe(200);
      });
    });
 
    describe('GET /hosts/:hostId', () => {
      it('should show a host by id', async () => {
        const response = await request(app).get(`/hosts/${createdHost.id}`);
 
        expect(response.statusCode).toBe(200);
 
        expect(response.body.name).toBe(createdHost.name);
      });
 
      it('should not show a host with invalid id', async () => {
        const response = await request(app).get(`/hosts/x`);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to read a host');
      });
    });
 
    describe('PUT /hosts/:hostId', () => {
      it('should update a host', async () => {
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .send(updatedHost);
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should list an updated host', async () => {
        const response = await request(app).get('/hosts');
 
        const hasValidHost = response.body.some(
          (host) => host.address === updatedHost.address
        );
 
        expect(hasValidHost).toBeTruthy();
      });
 
      it('should not update a host without name or address', async () => {
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .send({
            name: 'Cloudflare DNS',
          });
 
        expect(response.statusCode).toBe(400);
      });
 
      it('should not update a host with invalid id', async () => {
        const response = await request(app).put(`/hosts/x`).send(updatedHost);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to update a host');
      });
    });
 
    describe('DELETE /hosts/:hostId', () => {
      it('should remove a host', async () => {
        const response = await request(app).delete(`/hosts/${createdHost.id}`);
 
        expect(response.statusCode).toBe(204);
      });
 
      it('should not delete a host with invalid id', async () => {
        const response = await request(app).delete(`/hosts/x`);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to delete a host');
      });
    });
  });
});
 
/codes/expressjs/monitor-app-api/back/src/routes.test.js
import request from 'supertest';
import app from './index.js';
 
let createdHost;
 
const newHost = {
  name: 'DNS Server',
  address: '1.1.1.1',
};
 
const updatedHost = {
  name: 'Cloudflare DNS',
  address: '1.1.1.1',
};
 
describe('Moniotr App', () => {
  describe('Hosts Endpoints', () => {
    describe('POST /hosts', () => {
      it('should create a new host', async () => {
        const response = await request(app).post('/hosts').send(newHost);
 
        createdHost = response.body;
 
        expect(response.statusCode).toBe(201);
      });
 
      it('should not create a new host without name or address', async () => {
        const response = await request(app).post('/hosts').send({
          name: 'DNS Server',
        });
 
        expect(response.statusCode).toBe(400);
      });
    });
 
    describe('GET /hosts', () => {
      it('should show all hosts', async () => {
        const response = await request(app).get('/hosts');
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should list the valid host', async () => {
        const response = await request(app).get('/hosts');
 
        const hasValidHost = response.body.some(
          (host) => host.address === createdHost.address
        );
 
        expect(hasValidHost).toBeTruthy();
      });
 
      it('should show all hosts by name', async () => {
        const response = await request(app).get('/hosts?name=DNS');
 
        expect(response.statusCode).toBe(200);
      });
    });
 
    describe('GET /hosts/:hostId', () => {
      it('should show a host by id', async () => {
        const response = await request(app).get(`/hosts/${createdHost.id}`);
 
        expect(response.statusCode).toBe(200);
 
        expect(response.body.name).toBe(createdHost.name);
      });
 
      it('should not show a host with invalid id', async () => {
        const response = await request(app).get(`/hosts/x`);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to read a host');
      });
    });
 
    describe('PUT /hosts/:hostId', () => {
      it('should update a host', async () => {
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .send(updatedHost);
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should list an updated host', async () => {
        const response = await request(app).get('/hosts');
 
        const hasValidHost = response.body.some(
          (host) => host.address === updatedHost.address
        );
 
        expect(hasValidHost).toBeTruthy();
      });
 
      it('should not update a host without name or address', async () => {
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .send({
            name: 'Cloudflare DNS',
          });
 
        expect(response.statusCode).toBe(400);
      });
 
      it('should not update a host with invalid id', async () => {
        const response = await request(app).put(`/hosts/x`).send(updatedHost);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to update a host');
      });
    });
 
    describe('DELETE /hosts/:hostId', () => {
      it('should remove a host', async () => {
        const response = await request(app).delete(`/hosts/${createdHost.id}`);
 
        expect(response.statusCode).toBe(204);
      });
 
      it('should not delete a host with invalid id', async () => {
        const response = await request(app).delete(`/hosts/x`);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to delete a host');
      });
    });
  });
});
 

.skip()

describe.skip()
 
it.skip()
describe.skip()
 
it.skip()

Cobertura de Testes

/codes/expressjs/monitor-app-api/back/package.json
{
  "name": "invest-app",
  "type": "module",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "test": "node --experimental-vm-modules ./node_modules/.bin/jest src",
    "test:coverage": "node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
  },
  "jest": {
    "collectCoverage": true,
    "testTimeout": 20000,
    "coverageReporters": [
      "json",
      "html"
    ]
  },
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "express-async-errors": "^3.1.1",
    "morgan": "^1.10.0",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "supertest": "^6.3.4"
  }
}
 
/codes/expressjs/monitor-app-api/back/package.json
{
  "name": "invest-app",
  "type": "module",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "test": "node --experimental-vm-modules ./node_modules/.bin/jest src",
    "test:coverage": "node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
  },
  "jest": {
    "collectCoverage": true,
    "testTimeout": 20000,
    "coverageReporters": [
      "json",
      "html"
    ]
  },
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "express-async-errors": "^3.1.1",
    "morgan": "^1.10.0",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "supertest": "^6.3.4"
  }
}
 
$ npm run test:coverage
$ npm run test:coverage

Front-end Web

Open in GitHub

Arquivos
front
├── css
│   └── style.css
├── index.html
├── js
│   ├── components
│   │   ├── HostForm.js
│   │   ├── HostTableRow.js
│   │   └── Modal.js
│   ├── lib
│   │   ├── dom.js
│   │   └── hosts.js
│   ├── main.js
│   └── services
│       └── storage.js
├── package-lock.json
├── package.json
├── public
│   └── vite.svg
└── vite.config.js
Arquivos
front
├── css
│   └── style.css
├── index.html
├── js
│   ├── components
│   │   ├── HostForm.js
│   │   ├── HostTableRow.js
│   │   └── Modal.js
│   ├── lib
│   │   ├── dom.js
│   │   └── hosts.js
│   ├── main.js
│   └── services
│       └── storage.js
├── package-lock.json
├── package.json
├── public
│   └── vite.svg
└── vite.config.js
/codes/expressjs/monitor-app-api/front/vite.config.js
import { defineConfig } from 'vite';
 
// https://vitejs.dev/config/
export default defineConfig({
  server: {
    open: 'index.html',
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});
 
/codes/expressjs/monitor-app-api/front/vite.config.js
import { defineConfig } from 'vite';
 
// https://vitejs.dev/config/
export default defineConfig({
  server: {
    open: 'index.html',
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});
 
/codes/expressjs/monitor-app-api/front/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Monitor App</title>
    <script src="https://code.iconify.design/3/3.1.0/iconify.min.js"></script>
  </head>
  <body>
    <nav class="navbar navbar-expand-lg bg-body-tertiary">
      <div class="container">
        <a class="navbar-brand" href="#">Monitor App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="#">Home</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>
 
    <div class="container">
      <h1 class="text-center m-0 p-5">Monitor App</h1>
 
      <div class="card table-hosts">
        <div class="card-header">
          <h5 class="text-center">
            Hosts
            <div
              class="float-end create-host-event lh-base"
              data-bs-toggle="offcanvas"
              data-bs-target="#offcanvasRight"
              aria-controls="offcanvasRight"
            >
              <iconify-icon icon="bx:plus"></iconify-icon>
            </div>
          </h5>
        </div>
        <div class="card-body">
          <table class="table table-hosts">
            <thead>
              <tr>
                <th>Nome</th>
                <th>Endereço</th>
                <th></th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
        </div>
      </div>
    </div>
 
    <script type="module" src="/js/main.js"></script>
  </body>
</html>
 
/codes/expressjs/monitor-app-api/front/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Monitor App</title>
    <script src="https://code.iconify.design/3/3.1.0/iconify.min.js"></script>
  </head>
  <body>
    <nav class="navbar navbar-expand-lg bg-body-tertiary">
      <div class="container">
        <a class="navbar-brand" href="#">Monitor App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="#">Home</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>
 
    <div class="container">
      <h1 class="text-center m-0 p-5">Monitor App</h1>
 
      <div class="card table-hosts">
        <div class="card-header">
          <h5 class="text-center">
            Hosts
            <div
              class="float-end create-host-event lh-base"
              data-bs-toggle="offcanvas"
              data-bs-target="#offcanvasRight"
              aria-controls="offcanvasRight"
            >
              <iconify-icon icon="bx:plus"></iconify-icon>
            </div>
          </h5>
        </div>
        <div class="card-body">
          <table class="table table-hosts">
            <thead>
              <tr>
                <th>Nome</th>
                <th>Endereço</th>
                <th></th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
        </div>
      </div>
    </div>
 
    <script type="module" src="/js/main.js"></script>
  </body>
</html>
 
/codes/expressjs/monitor-app-api/front/css/style.css
iconify-icon:hover {
  cursor: pointer;
}
 
/codes/expressjs/monitor-app-api/front/css/style.css
iconify-icon:hover {
  cursor: pointer;
}
 
/codes/expressjs/monitor-app-api/front/js/main.js
import 'bootstrap';
import 'iconify-icon';
 
import HostForm from './components/HostForm';
import Modal from './components/Modal';
import Hosts from './lib/hosts';
 
import 'bootstrap/dist/css/bootstrap.css';
 
import '../css/style.css';
 
Hosts.load();
 
HostForm.create();
 
Modal.create();
 
/codes/expressjs/monitor-app-api/front/js/main.js
import 'bootstrap';
import 'iconify-icon';
 
import HostForm from './components/HostForm';
import Modal from './components/Modal';
import Hosts from './lib/hosts';
 
import 'bootstrap/dist/css/bootstrap.css';
 
import '../css/style.css';
 
Hosts.load();
 
HostForm.create();
 
Modal.create();
 
/codes/expressjs/monitor-app-api/front/js/services/storage.js
const API_URL = '/api';
 
async function create(resource, data) {
  resource = `${API_URL}/${resource}`;
 
  const options = {
    headers: {
      'Content-Type': 'application/json',
    },
    method: 'post',
    body: JSON.stringify(data),
  };
 
  const res = await fetch(resource, options);
 
  const createdData = await res.json();
 
  return createdData;
}
 
async function read(resource) {
  resource = `${API_URL}/${resource}`;
 
  const res = await fetch(resource);
 
  return await res.json();
}
 
async function update(resource, data) {
  resource = `${API_URL}/${resource}`;
 
  const options = {
    headers: {
      'Content-Type': 'application/json',
    },
    method: 'put',
    body: JSON.stringify(data),
  };
 
  const res = await fetch(resource, options);
 
  const updatedData = await res.json();
 
  return updatedData;
}
 
async function remove(resource) {
  resource = `${API_URL}/${resource}`;
 
  const options = {
    method: 'delete',
  };
 
  const res = await fetch(resource, options);
 
  return res.ok;
}
 
export default { create, read, update, remove };
 
/codes/expressjs/monitor-app-api/front/js/services/storage.js
const API_URL = '/api';
 
async function create(resource, data) {
  resource = `${API_URL}/${resource}`;
 
  const options = {
    headers: {
      'Content-Type': 'application/json',
    },
    method: 'post',
    body: JSON.stringify(data),
  };
 
  const res = await fetch(resource, options);
 
  const createdData = await res.json();
 
  return createdData;
}
 
async function read(resource) {
  resource = `${API_URL}/${resource}`;
 
  const res = await fetch(resource);
 
  return await res.json();
}
 
async function update(resource, data) {
  resource = `${API_URL}/${resource}`;
 
  const options = {
    headers: {
      'Content-Type': 'application/json',
    },
    method: 'put',
    body: JSON.stringify(data),
  };
 
  const res = await fetch(resource, options);
 
  const updatedData = await res.json();
 
  return updatedData;
}
 
async function remove(resource) {
  resource = `${API_URL}/${resource}`;
 
  const options = {
    method: 'delete',
  };
 
  const res = await fetch(resource, options);
 
  return res.ok;
}
 
export default { create, read, update, remove };
 

Editar esta página