click fraud detection
click fraud detection
Blog Case

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

BLOG
CASE
112
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
СОДЕРЖАНИЕ
СТАТЬИ
Сервер
Выводы
Что такое мобильное приложение для сайта
Для удобства мобильной аудитории многие предприятия с веб-представительством создают мобильные приложения для собственного сайта. Это…
Алексей Варламов
Алексей Варламов
Онлайн игра Блэк-Джек. Часть 2.
Дмитрий Жариков
Дмитрий Жариков
ПОЛУЧАТЬ ИНТЕРЕСНЫЕ СТАТЬИ
Уже подписались 248 человек
Автор
112
0
Дмитрий Жариков
Дмитрий
Жариков
most
Popular
Возможно
В этой статье я кратко расскажу, как оптимизировать сайты-новостники, чтобы они нравились и пользователям, и…
Юлия Телижняк
Юлия Телижняк
Дизайн сайта – это нечто большее, чем просто его внешний вид. От дизайна зависит первое…
Алексей Варламов
Алексей Варламов
Основной риск, связанный с переездом, – просадка веб-ресурса по позициям, а некоторые важные страницы могут…
Алексей Варламов
Алексей Варламов
Давайте начнем
беседу!
КОММЕНТАРИИ0
ОСТАВИТЬ КОММЕНТАРИЙ К СТАТЬЕ
ПОДПИСЫВАЙТЕСЬ НА РАССЫЛКУ АЙТЫЖБЛОГ
ХОТИТЕ ПОЛУЧАТЬ 
ИНТЕРЕСНЫЕ СТАТЬИ?
Уже подписались 248 человек
313
ПОПИСЧИКОВ
ЧИТАТЬ
4295
ПОПИСЧИКОВ
СЛЕДИТЬ
9307
ПОПИСЧИКОВ
СЛЕДИТЬ