click fraud detection
click fraud detection
Blog Case

IM на основе NodeJS Express и Angular 7. Часть 4

BLOG
CASE
309
0
0/ 5stars
0/5
Время чтения: 20 минут

Сервер

Вынесем подключение к базе данных в отдельный модуль db.ts.

const Pool = require('pg').Pool
const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'curtains',
  password: '1q2w3e',
  port: 5432,
})

export default pool;

Использование будет следующим:

import pool from './db';
...
let result = await pool.query(...)

Для запросов можно в db.ts использовать Promise.

export default {
  query(text, params){
    return new Promise((resolve, reject) => {
      pool.query(text, params)
      .then((res) => {
        resolve(res);
      })
      .catch((err) => {
        reject(err);
      })
    })
  }

И использовать его так:

import db from '../db';
let result = await db.query(...)

Метод для получения списка товаров с постраничной навигацией.

    async getList(req: Request, res: Response){
      const id = req.params.id;
      const offset = req.params.perpage * req.params.page;
      const sql = 'SELECT * FROM shop_good limit $1 offset $2';
      try {
        const result = await pool.query(sql,[req.params.perpage,offset]);
        let out = {result: [], count: 0};
        for(let row of result.rows){
          let good = await Good.serialize(row);
          out.result.push(good);
        }
        const sql_count = 'SELECT count(*) FROM shop_good';
        const result_count = await pool.query(sql_count);
        out.count = result_count.rows[0].count;
        res.json(out);
        } catch (err) {
          console.log(err.stack)
          res.status(200).json({status: 1, message: 'Error!'}) 
  }
}

Мы объявляем функцию getList аннотированную оператором async т.к. внутри функции используем асинхронные вызовы (запросы к БД). В общем случае, после вызова функция async возвращает Promise. Когда результат получен, Promise завершается, возвращая полученное значение. Если внутри функции встречается оператор await, то выполнение функции приостанавливается и ожидается ответ от переданного Promise, затем возобновляя выполнение функции async и возвращая полученное значение.

Запрос формируется с учетом двух параметров: количества элементов на странице и номера страницы, который используется для определения смещения курсора (результат умножения номера страницы на количество элементов на ней).

В первом запросе мы получаем список всех товаров. Во втором – их количество.

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

Эта фунция вызывается в цикле при проходе по массиву полученных товаров и заносит результат серилизации в массив out.

  for(let row of result.rows){
          let good = await Good.serialize(row);
          out.result.push(good);
        }

Класс Good выглядит следующим образом:

import {Request, Response} from "express";
import pool from './db';

class Good{
  id: number;
  name: string;
  name_slug: string;
  desc: string;
  subcategory_id: number;
  subcategory: any;
  category: any;
  image: any;

  constructor(){}

  public static async serialize(json_obj: any){
    let obj = new Good();
    obj.id = json_obj.id;
    obj.name = json_obj.name;
    obj.name_slug = json_obj.name_slug;
    obj.desc = json_obj.desc;
    obj.subcategory_id = json_obj.subcategory_id;

    /// заполняем подкатегорию
    let sql_sub = 'SELECT * FROM shop_subcategory where id=$1';
    let result_sub = await pool.query(sql_sub,[obj.subcategory_id]);
    obj.subcategory = {'name': result_sub.rows[0].name, 'name_slug':
result_sub.rows[0].name_slug};

    // заполняем категорию
    let sql_cat = 'SELECT * FROM shop_category where id=$1';
    let result_cat = await pool.query(sql_cat,[result_sub.rows[0].category_id]);
    obj.category = {'name': result_cat.rows[0].name, 'name_slug': result_cat.rows[0].name_slug};

    // заполняем изображение
    let sql_image = 'SELECT * FROM shop_image where good_id=$1';
    let result_image = await pool.query(sql_image,[obj.id]);
    obj.image = {'image': result_image.rows[0].image};    
    return obj;
  }
}

Функция serialize представляет собой фабрику, которая создает объект Good, наполняет его из переданного в конструктор json объекта и генерирует три запроса в БД для получения информации о категории, подкатегории и изображении.

Вот так выглядит получившейся json на странице:

Реализуем функцию получения детальной информации о товаре по адресу http://localhost:3000/good/10.

  async getOne(req: Request, res: Response){
       const id = req.params.id;
       const sql = 'SELECT * FROM shop_good where id=$1';
       try {
        const result = await pool.query(sql,[id]);
        let good = await Good.serialize(result.rows[0]);
          res.json(good);
       } catch (err) {
          console.log(err.stack)
          res.json({status: 1, message: 'Error!'});
       }
    }

Мы применили конструкцию try/catch для отработки случаев несуществующего товара и вывода в консоль ошибки:

TypeError: Cannot read property 'id' of undefined


Вывод товаров по категории

Добавим необязательный параметр категории в маршрут.

app.get("/good/list/:page/:perpage/:category?", good_api.getList);


Вставим условие в метод getList, в котором проверим на существование параметра category и соответственно изменим запрос при его существовании.

 if(req.params.category){
        var sql = 'SELECT * FROM shop_good where subcategory_id=$3 limit $1 offset $2';
        var result = await pool.query(sql,[req.params.perpage, offset, req.params.category]);
      } else {
        var sql = 'SELECT * FROM shop_good limit $1 offset $2';
        var result = await pool.query(sql,[req.params.perpage,offset]);
      }

Поиск товаров по заголовку

Реализуем новый маршрут и метод поиска по переданному параметру поискового слова.

Маршрут:

app.get("/good/search/:key", good_api.search);


Метод:

   async search(req: Request, res: Response){
    var sql = "SELECT * FROM shop_good where name ilike $1 limit 10";
    var result = await pool.query(sql,[req.params.key+'%']);
    try {
        let out = {result: [], count: 0};
        for(let row of result.rows){
          let good = await Good.serialize(row);
          out.result.push(good);
        }
        res.json(out);
      } catch (err){
        console.log(err.stack)
        res.status(200).json({status: 1, message: 'Error!'}) 
      }    
}

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

Запросы будут выглядеть так:

SELECT * FROM shop_good where name ilike ‘Avi%’ limit 10


Сохранение заказа

При сохранении заказа данные будут переданы в формате json в теле post запроса и внутри метода-контроллера будут доступны через объект req.body. 

Предположим, что данные будут следующей структуры:

{

     ‘name’ : ‘Иван’,

     ‘phone’: ‘095647364’,

     ‘email’: ‘ivan@gmail.com’,

     ‘good_id’: ‘23’

}


Роутинг:

app.post("/makeOrder", good_api.makeOrder);

Тогда для этой структуры метод добавления записи в таблицу shop_order будет следующий: 

  async makeOrder(req: Request, res: Response){
    const sql = 'INSERT INTO shop_order(good_id, phone, name, email) VALUES($1, $2,
$3, $4) RETURNING *'
    const values = [req.body.good_id, req.body.phone, req.body.name, req.body.email]
    try {
        const result = await pool.query(sql, values)
        res.status(200).json({status: 0, message: 'Ok'}) 
      } catch (err) {
        console.log(err.stack)
        res.status(200).json({status: 1, message: 'Error!'}) 
      }
   }

Для удаления заказа можно создать следующий метод:

   async deleteOrder(req: Request, res: Response){
    const sql = 'DELETE FROM shop_order WHERE id=$1 returning *'
    const values = [req.body.order_id]
    try {
        const result = await pool.query(sql, values)
        res.status(200).json({status: 0, message: 'Ok'}) 
      } catch (err) {
        console.log(err.stack)
        res.status(200).json({status: 1, message: 'Error!'}) 
      }
   }

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

Выводы

В данной статье мы рассмотрели процесс работы с базой данных PostgreSQL для создания RESTfull API сервиса. Методы передачи обязательных и необязательных параметров адресной строки. Механизмы серилизации и наполнения объектов при помощи фабричного метода. Формирование SQL-запросов поиска, добавления и удаления записей внутри функций-контроллеров фреймворка Express.

У ВАС ОСТАЛИСЬ ВОПРОСЫ?

Оставьте ваши контактные данные. Наш менеджер свяжется и проконсультирует вас.

ПОЛУЧИТЬ КОНСУЛЬТАЦИЮ

Наш менеджер свяжется с Вами в ближайшее время

0/5
Проголосовало людей: 0
СОДЕРЖАНИЕ
СТАТЬИ
Сервер
Выводы
Сайт для некоммерческой организации: зачем нужен, с чего начать, как запустить и где заказать
Сайт для некоммерческой организации – это эффективный способ привлечения большой аудитории. Он дает возможность рассказать…
Wezom
Wezom
Что такое SPA-приложения
Если вы хотите лучше разобраться в этой теме, то данная статья будет вам полезна. В…
Алексей Варламов
Алексей Варламов
Лучшие PHP-фреймворки, которые упрощают разработку в 2019 году
Фреймворки PHP – это программные платформы, которые значительно облегчают и ускоряют разработку сайтов, web- и…
Алексей Варламов
Алексей Варламов
Что такое модернизация сайта и когда она необходима
В этой статье мы расскажем о том, какие задачи позволяет решить данный вид работ, какие…
Алексей Варламов
Алексей Варламов
ПОЛУЧАТЬ ИНТЕРЕСНЫЕ СТАТЬИ
Уже подписались 260 человек
Автор
309
0
Дмитрий Жариков
Дмитрий
Жариков
most
Popular
Возможно
Этот год начался с отличных новостей, которыми мы спешим с вами поделиться! Благодаря приложениям, разработанным…
Из статьи вы узнаете 9 способов создать сильный бренд и 2 способа создать слабый. Но…
Надежда Тихонюк
Надежда Тихонюк
Все санкции от Google плохо действуют на сайты, будь то обновления алгоритмов, способные немного понизить…
Екатерина Шведа
Екатерина Шведа
Давайте начнем
беседу!
КОММЕНТАРИИ0
ОСТАВИТЬ КОММЕНТАРИЙ К СТАТЬЕ
ПОДПИСЫВАЙТЕСЬ НА РАССЫЛКУ АЙТЫЖБЛОГ
ХОТИТЕ ПОЛУЧАТЬ 
ИНТЕРЕСНЫЕ СТАТЬИ?
Уже подписались 260 человек
313
ПОПИСЧИКОВ
ЧИТАТЬ
4295
ПОПИСЧИКОВ
СЛЕДИТЬ
9307
ПОПИСЧИКОВ
СЛЕДИТЬ