Бэкэнд разработка

Node-Postgres

Также известная как pg, это популярная клиентская библиотека PostgreSQL для Node.js. Он предоставляет способ подключения к базе данных PostgreSQL из приложения Node.js и выполнения различных операций с базой данных.

Одной из ключевых функций pg является объединение пулов соединений — метод, используемый для повышения производительности и масштабируемости приложений баз данных за счет повторного использования соединения с базой данных вместо создания нового соединения для каждого запрос. Это поддерживает пул открытых подключений к базе данных, которые совместно используются клиентами.

// db/index.js
const { Pool } = require("pg");

if (!process.env.PGDATABASE) {
  throw new Error("No PGDATABASE configured");
}

module.exports = new Pool();

pg со экспресс-доставкой

После установки pg и express вы можете настроить файл app.js, как показано ниже. Используя модель MVC, контроллер — это место, где мы имеем дело с запросом | цикл ответа, и модель взаимодействует с базой данных через пул соединений.

// app.js
const express = require('express');
const { sendSnacks } = require('./controllers/snacks.js')
const app = express();

app.get('/api/snacks', sendSnacks);

// controllers/snacks.js
const selectSnacks = require('../models/snacksDb.js')

exports.sendSnacks = (req, res) => {
  snacksDb()
  .then(snacks => res.status(200).send({ snacks }));
}

// models/snacksDb.js
const db = require('../db/index.js')
exports.snacksDb = () => {
 return db.query('SELECT * FROM snacks_table')
        .then(results => return results.row);

При работе с определенными параметрическими конечными точками, такими как ‘/snacks/:snack_id’, мы можем получить доступ к параметрам запроса, используя req.params в функции контроллера. Мы избегаем использования литералов строковых шаблонов или конкатенации строк, так как это может привести к SQL-инъекции.

Вместо этого мы могли бы использовать следующий код:

// models/snacks.js
exports.selectSnackById = (snack_id) => {
  return db
    .query("SELECT * FROM snacks WHERE snack_id = $1;", [snack_id])
    .then((result) => result.rows[0]);
};

Все вышеперечисленное было для запроса на получение, мы можем следовать тому же для методов post, put, delete. Для метода post/put контроллер затем отвечает за передачу тела запроса (req.body) в модель и отправку только что вставленной/обновленной закуски клиенту.

Мы хотим использовать промежуточное ПО body-parser для разбора тела запроса.

Заполнение баз данных

Заполнение баз данных вручную занимает много времени, поэтому мы делаем это программно, а не с помощью параметризованного запроса, который использует заполнители или параметры для передачи значений в запрос во время выполнения.

Для этого мы можем установить pg-format, что позволит нам вставлять несколько значений, как показано ниже:

const format = require('pg-format');
const db = require('../db/index.js');

const itemsInsertStr = format(
  `INSERT INTO items
    (item_name, quantity)
  VALUES
    %L
  RETURNING *;`,
  nestedArrOfValues // [['item-A', 3], ['item-B', 5], [...], ...]
);

db.query(itemsInsertStr).then((itemsInsertResult) => itemsInsertResult.rows[0]);

формат принимает два аргумента — SQL-запрос и вложенный массив значений, которые можно создать с помощью функции map.

Мы можем создать начальную функцию, которая удаляет таблицы, создает таблицы и заполняет базу данных исходными данными.

Супертест

До этого момента мы проводили модульное тестирование с помощью Jest как часть TDD, интеграционное тестирование гарантирует, что несколько частей проекта работают вместе. Супертест — это тип интеграционного тестирования, используемый в сочетании с Jest для отправки запроса на сервер и проверки правильности ответа.

Установите супертест и пошутите над зависимостью разработчика, поскольку они специально используются для тестирования, а не для фактической работы приложения.

Чтобы сделать тестовый запрос к приложению, переменная request (супертест) передается app в качестве аргумента в блоке jest test — если сервер еще не прослушивает соединения, то он привязан к временному порту, поэтому нет нужно следить за портами.

describe('GET /api/items', () => {
  it('responds with object containing all items', () => {
    return request(app)
    .get('/api/items')
    .expect(200)
    .then((res) => {
      expect(res.body.items.length).toBe(4);
    });
  });
});

Поскольку мы продолжаем тестировать различные конечные точки, особенно post или put, которые изменяют исходные данные, наше тестирование может стать ненадежным, поэтому мы можем повторно заполнить базу данных, используя следующий набор тестов:

beforeEach(() => seed(data));

afterAll(() => db.end());

Функции beforeEach позволяют повторно заполнить базу данных с помощью нашей функции seed, а функция afterAll обеспечивает закрытие соединения с пулом и делает его доступным для повторного использования другими клиентами в пуле.

Обработка ошибок с помощью Express

Мы можем отправить сообщение об ошибке с контроллера, если что-то пойдет не так, как планировалось, но это будет повторяться для каждого контроллера. Вместо этого мы можем использовать функцию next(), которая может быть вызвана с аргументом для отправки в промежуточное программное обеспечение для обработки ошибок или без аргумента, который переходит к следующей функции промежуточного программного обеспечения.

Express имеет обработчик ошибок по умолчанию, если не определена другая функция промежуточного программного обеспечения для обработки ошибок, которая по умолчанию отправит статус 500 с ошибкой, с которой был вызван next.

PSQL также выдает ошибку, но они не совпадают с кодами состояния HTTP.

// app.js
app.use((err, req, res, next) => {
  if (err.code === '22P02') {
    res.status(400).send({ msg: 'Bad Request' });
  } else res.status(500).send({ msg: 'Internal Server Error' });
});

Если в SQL-запросе нет ошибок, но запрос не возвращает никаких данных/строк, мы можем создать собственный обработчик ошибок.

Базы данных для тестирования и разработки

pg находит базу данных для подключения из переменных окружения, которые обычно задаются в сценариях package.json, но есть пакет npm под названием dotenv, который может помочь нам настроить переменные окружения.

После его установки мы создаем файл с расширением «.env» для хранения наших переменных и добавляем его в файл gitignore, чтобы предотвратить его отправку на GitHub.

После того, как мы настроим соединение с базой данных через пул, мы можем потребовать dotenv и вызвать метод конфигурации, как показано ниже:

const { Pool } = require('pg');

const ENV = process.env.NODE_ENV || 'development';

require('dotenv').config({
  path: `${__dirname}/../.env.${ENV}`,
});

if (!process.env.PGDATABASE) {
  throw new Error('PGDATABASE not set');
}

module.exports = new Pool();

ENV устанавливается либо на тест при прогоне через супертест, либо на разработку.

// .env.test
PGDATABASE=database_name_developement
// .env.development
PGDATABASE=database_name_test

У нас есть отдельная база данных для тестирования, чтобы пользователи не сталкивались с непредвиденными изменениями данных каждый раз, когда они повторно заполняются или если тест вносит изменения в базу данных.

Кроме того, разработчики могут использовать меньшие данные, которые являются более управляемым подмножеством данных разработки, что означает, что заполнение может происходить быстрее.

Вы можете создать одну повторно используемую исходную функцию, которая может заполнить как тестовую базу данных, так и базу данных разработки.

Ссылки на неделю

Эмоциональная проверка 🌏

Прошедшая неделя была вихрем обучения с множеством сложных концепций бэкэнда, которые нужно было усвоить. Несмотря на интенсивность, все части соберутся вместе на следующей неделе, когда я начну проектную неделю, в течение которой я буду создавать сервер с нуля, чтобы через несколько недель подключиться к моему внешнему проекту.

Я рад, что принял решение писать эти еженедельные статьи, так как они служат полезным обобщением всего, что я узнал и понял за последнюю неделю, и, надеюсь, они имеют смысл для всех, кто их читает, и дают некоторое представление о различных понятия и темы.

После недели нечеткого зрения я проверил свое зрение и оказалось, что мое зрение действительно улучшается, но мои линзы сошли с ума, т.е. отражающее покрытие треснуло из-за высоких температур. Если вы носите очки, не принимайте душ в них.

Прыжок во времени