Translate

Поиск по этому блогу

Показаны сообщения с ярлыком MongoDB. Показать все сообщения
Показаны сообщения с ярлыком MongoDB. Показать все сообщения

пятница, 21 декабря 2018 г.

Бэкенд на Node+Express+MongoDB

Сегодня мы создадим бэкенд-часть приложения на стэке NodeJS+ExpressJS+MongoDB



Это позволит в дальнейшем использовать наш код для развертывания приложений, в том числе и с использованием ReactJS.

Я уже рассказывал о подобных приложениях своих постах - Блог на NodeJS Express MongoDB

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

Нам потребуется установленная платформа Node.js и менеджер пакетов NPM. О том как их скачать, установить и настроить я рассказывал в своих постах ранее - Простые Советы - JAVASCRIPT.

Для того чтобы убедиться что у вас все установлено и настроено правильно выполним последовательно в КС (терминал - командная строка) следующие команды:

node -v
npm -v
mongod --version


Увидим что-то подобное.



У вас будут свои версии.

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

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

mongod


Вы увидите, что то похожее:



Нам интересно последняя строчка выделенная красным. Она говорит о том. что база готова к работе и запущена на порте - 27017

Не закрывая это окно команд. Откроем еще одно - ⊞ Win + R Введем - cmd и нажмем Enter.

В этом окне введем последовательно команды:

mongo
show dbs




Получим список всех существующих баз данных.

Для того, чтобы остановить работу сервера нам нужно нажать сочетание клавиш Ctrl + C. Для остановки локального сервера (на будущее) требуется ввести еще и подтверждение - y. Затем- нажать Enter.

Перейдем к разработке серверной части приложения.

Мы будем использовать фреймворк Express

Вначале создадим папку нашего проекта и перейдем в нее:

mkdir server-side
cd server-side


Если вы используете Git для разработки, то я рекомендую сразу создать файл .gitignore, куда сразу добавить папку с модулями ноды - node_modules.

echo node_modules/ >> .gitignore




Если вы работаете в VS-code, то введите:

code .


Или иным способом откройте папку в вашем любимом редакторе кода.

В дальнейшем я буду использовать терминал VS-code, но вы можете легко использовать и КС.

Инициализация приложения

На этом этапе мы создадим папку package.json - со всеми зависимостями.

npm init


Или с флагом -y.

Об этом я очень подробно рассказывал в посте Блог на NodeJS Express MongoDB ( I ).

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



Устанавливаем Express

Для этого в КС (терминале), находясь в рабочей папке проекта:

npm install express --save


Установится фреймворк и добавится в зависимости нашего проекта - package.json



Файл index.js - точка входа.

Так как, мы когда инициализировали наш проект (создавали файл package.json), оставили по умолчанию точку входа - "main": "index.js", то нам нужно создать файл index.js в корне нашего проекта.

В нем мы сразу подключим фреймворк Express методом require. Здесь же создадим константу app, которая будет выполнять серверную часть нашего приложения.


 const express = require('express');
 const app = express();



Добавим метод get, которым будем выводить все посты на страницу по запросу на адрес:
http://localhost:3000/posts
И поставим прослушивание порта нашим сервером:


app.get('/posts', function(req, res) {
     return res.send(posts);
 });

app.listen(3000, ()=> console.log('Server running on 3000 port'));



Все вместе index.js:


 const express = require('express');
 const app = express();
 
  app.get('/posts', function(req, res) {
     return res.send(posts);
 });
 
  app.listen(3000, ()=> console.log('Server running on 3000 port'));



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

Создание тестовых записей

В файл index.js, сразу после инициализации сервера ( константа app), добавим константу posts с тестовыми записями наших постов.


  const posts = [
     {
         title:  "lorem ipsum1",
         text: "Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet consectetur adipisicing elitconsectetur adipisicing elit. Pariatur fugit fuga ut voluptas nostrum ullam eum accusantium consequuntur necessitatibus dolores. Pariatur hic officiis numquam ipsam perspiciatis officia maxime iusto vitae."
     },
     {
         title:  "lorem ipsum2",
         text: "Lorem ipsum dolor sit amet  Pariatur fugit fuga ut voluptas nostrum ullam eum accusantium consequuntur necessitatibus dolores. Pariatur hic officiis numquam ipsam perspiciatis officia maxime iusto vitae."
     },
     {
         title:  "lorem ipsum3",
         text: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Pariatur fugit fuga ut voluptas nostrum ullam eum accusantium consequuntur necessitatibus dolores. Pariatur hic officiis numquam ipsam perspiciatis officia maxime iusto vitae."
     },
     {
         title:  "lorem ipsum4",
         text: "Lorem ipsum dolor sitLorem ipsum dolor sit amet consectetur adipisicing elit amet consectetur adipisicing elit. Pariatur fugit fuga ut voluptas nostrum ullam eum accusantium consequuntur necessitatibus dolores. Pariatur hic officiis numquam ipsam perspiciatis officia maxime iusto vitae."
     }    
 ];



Запускаем сервер:

node index.js


Переходим по адресу: http://localhost:3000/posts



Видим все посты на странице.

У меня в браузере добавлено расширение для более красивого показа формата JSON - JSONView. У вас это может выглядеть просто строками, которые содержит переменная posts.

На этом этапе я инициализировал git и добавил все изменения в репозиторий на сайте gitHub.com - server-side-abcinblog ci -m (здесь и далее - коммит) "output all tests posts"

Теперь можно сделать вывод единственного поста на страницу по id

Для этого мы добавим метод get с двумя параметрами. Первый - путь, где через /:id мы указываем изменчивый параметр и получаем его значение из строки запроса const id = req.params.id;


app.get('/posts/:id', function(req, res) {
    const id = req.params.id;
    res.send(posts[id]);
});



Запускаем сервер, переходим по адресу http://localhost:3000/posts/1 и видим:



Если изменить в адресной строке id поста (цифра отмечена красным), то мы увидим соответствующий пост. Нумерация постов начинается по id с нуля, а мы поставили первым постом не ноль, а "пост номер один", поэтому будет выглядеть так. Мне кажется. что так понятнее и более наглядно.

Теперь разберемся с созданием новой записи. Для этого нам потребуется еще одна библиотека - body-pareser, которая поможет нам получать данные из тела запроса.

Более подробно узнать о работе с этой библиотекой можно узнать из официальной документации на gitHub и здесь.

Подключаем ее обычным способом и используем ее для написания метода post

const bodyParser = require('body-parser');
// после инициализации const app
 app.use(bodyParser.urlencoded({ extended: true}));
 app.use(bodyParser.json());
// в методах:
 app.post('/posts', function(req, res) {
    // получаем данные из тела запроса и сохраняем в конст.
    const data = req.body;
    // посмотрим что у нас там? 
    console.log(data);
    // добавляем полученные данные к постам
    posts.push(data);
    // чтобы не было бесконечного цикла - вернем все посты на страницу
    return res.send(posts);
});



После внесения изменений в файлы проекта не забывайте перезапускать сервер.
Ctrl + C и снова

node index.js


Для проверки будем использовать утилиту Postman. Я уже много раз рассказывал вам о работе с ней в моих постах. Это легко, просто и удобно. Если забыли, то посмотрите в поиске по ярлыкам.

Итак. Идем в Postman делаем GET для всех постов. Посмотрели и убедились, что все работает:



Теперь выберем запрос POSTи во вкладке body введем данные для нашего поста в формате - JSON.



Нажимаем Send и получаем ниже список со всеми постами, включая новый.



А в терминале наш console.log(data) выведет эти данные так:



Подключение к базе данных MongoDB и добавление первой тестовой записи.

Для работы с MongoDB мы будем использовать стороннюю библиотеку - mongoose, помощью который мы будем производить все манипуляции с базой данных. Для установки библиотеке в терминале (КС), в папке нашего проекта напишем команду:

npm install mongoose


Перед тем как добавить первую теcтовую запись, запустим сервер MongoDB:

mongod


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

В новом окне терминала:

mongo
show dbs


Итак, вы получите список всех созданных локально баз данных.

Удалить локальную базу данных MongoDB

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

use test1


Поле этого ввести команду:

db.dropDatabase()


Всё! База данных удалена. Для того, чтобы убедиться в этом, выведем еще раз список всех доступных баз данных:

show dbs


Все действия вы можете увидеть на скрине ниже.



Создание новой базы данных MongoDB

Здесь ве еще проще, чем можно подумать. Для создания новой базы данных я взял полностью готовый код на сайте mongoose и добавил его в наш файл index.js, предварительно я все там закомментировал.


const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/'server-side');

const Cat = mongoose.model('Cat', { name: String });

const kitty = new Cat({ name: 'Zildjian' });
kitty.save().then(() => console.log('meow'));



Красным, отмечено название создаваемой базы данных. Его можно изменить на ваше усмотрение. Если коротко разобрать то, что делает наш код, то:
  1. На первой строке - мы подключаем саму библиотеку.
  2. Ниже мы подключаемся к серверу и указываем конкретную базу данных.
  3. Третьей строкой создаем модель - таблицу, указывая к ней конкретную схему.
  4. После этого мы создаем экземпляр с названием данных.
  5. Сохраняем эти данные и после их успешного сохранения, отображаем пользователю конкретную информацию.
Если сейчас запустить сервер:

node index.js


В терминале (КС) мы увидим meow, как на фото ниже:



Давайте проверим, наличие баз данных (в другом окне):

show dbs


то мы увидим вновь созданную базу:



Установка Babel

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

npm install babel-cli babel-core babel-preset-env babel-preset-stage-0 --save-dev


  1. babel-core
  2. babel-cli
  3. babel-preset-stage-0
  4. babel-preset-env
На этом этапе все изменения вы можете посмотреть в репо - server-side-abcinblog ci -m"reate DB - local and test"

Для проверки работы установленных пакетов, мы можем удалить, или закомментировать весь код в файле index.js и добавить код написанный на ES6 для проверки:


const arr = [1, 2, 3, 4, 5, 6];
const result = arr.map(el => el * 3);
console.log(result);



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


{
    "presets": ["env","stage-0"]
}



В скрипты, файла package.json добавлю скипт, который будет запускать Babel и переводить наши файлы из файла index.js в ES5 и складывать готовые файлы в папку (которую он сам и создаст) - dist


 "scripts": {
    "build": "babel  index.js -d dist",
    "test": "echo \"Error: no test specified\" && exit 1"
  },



Теперь, для запуска нам нужно набрать команду:

npm run build


После запуска, у нас появится новая папка dist



Для конкатенации массивов таким способом - [...arr1, ...arr2], мы установим Babel-plugin: babel-plugin-transform-object-rest-spread

npm install --save-dev babel-plugin-transform-object-rest-spread


Для того, чтобы не писать несколько команд сразу в терминале, мы внесем изменения в скрипты в файле package.json


  "scripts": {
    "build": "babel  index.js -d dist",
    "start":"node dist/index.js", 
    "build-start": "npm run build && npm run start"
  },



  1. Первая команда, ранее нами добавленная, конвертирует код.
  2. Вторая команда запускает конвертированный файл из папки dist.
  3. Третья команда выполняется предыдущие две сразу, вместе.
Запусти наш код:

npm run build-start


В терминале увидим результат выполнения нашего кода:



Теперь мы можем писать код на ES6 используя все его базовые возможности, вроде импорта и деструктуризации. Если вы забыли, что это, то можно посмотреть мои поты по ES6 JavaScript.

На этом этапе все изменения вы можете посмотреть в репо - server-side-abcinblog ci -m"Babel"

Для того, чтобы наш проект имел более правильную структуру, я в корне проекта создал папку src и перенес в нее файл index.js из корня. В файле package.json я изменил сроку build в scripts следующим образом:


  "scripts": {
    "build": "babel  src -d dist",
    "start":"node dist/index.js", 
    "build-start": "npm run build && npm run start"
  },



Можно еще раз запустить:

npm run build-start


чтобы убедиться, что все работает правильно.

Создание модели

В папке src создадим папку для наших моделей - models и в ней файл - Post.js. Это модель, которая будет работать с таблицами наших статей. Нам нужно импортировать сам mongoose и Schema из библиотеки mongoose. Описываем схему для нашего поста. Нам требуется заголовок, собственно текст и дата создания записи. С помощью свойства timestamps будут добавляться эти даты.


import mongoose, { Schema } from 'mongoose';

const PostSchema = new Schema(
    {
        title: String,
        text: String,
    },
    {
        timestamps: true
    }
);
const Post = mongoose.model('Post', PostSchema);

export default Post;



В конце мы экспортируем модель Post по умолчанию. Теперь нам нужно подключиться к базе данных и создать первую запись. В файле src/index.js я все удалю и добавлю


import mongoose from 'mongoose';

import PostModel from './models/Post';

mongoose.connect('mongodb://localhost/server-side');

const post = new PostModel({
    title: "Первая запись",
    text: "Привет Мир"
});

post.save().then(() => console.log('OK'));



Здесь мне кажется. все понятно. Импортировали mongoose и саму модель PostModel. Создали экземпляр модели и указали тестовые данные. Чтобы сохранить саму запись, мы обращаемся к методу save(), который возвращает Promise.

Перед тем. как запустить код, стоит проверить, что у нас отсутствует база данных.

Так как, я уже создавал такую базу, то я выполнил ее удаление.



Запускаем код:

npm run build-start


В консоли вы увидите:



И если опять посмотреть весь список баз данных, то мы увидим вновь созданную:



Теперь выполним команды последовательно:
  1. use server-side
  2. show collections
  3. db.posts.find()




На этом этапе я сделал коммит и все изменения здесь - server-side-abcinblog ci -m"Connection with MongoDB and first tests record"

Запросы на добавление записи

В файле src/index.js подключим express и body-parser для обработки тела запроса.

Создаем приложение express

Подключаем body-parser для нашего express- приложения.

ВАЖНО - подключение к базе данных располагать сразу после создания сервера - выше остального кода.

Создаем обработчик для приема и обработки POST запросов.

Теперь вместо тестовой записи, мы будем принимать реальные данные от клиента, которые мы сохранили в переменно data

Вместо ответа в консоли мы вернет ответ в виде JSON.

Последней строкой, ставим приложение на прослушивание по определенному порту. В моем случае - 3000.


import mongoose from 'mongoose';
import express from 'express';
import bodyParser from 'body-parser';
import PostModel from './models/Post';

const app = express();

mongoose.connect('mongodb://localhost/server-side');

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.post('/posts', (req, res) => {
    const data = req.body;
    // console.log(req.body);
    const post = new PostModel({
        title: data.title,
        text: data.text
    });

    post.save().then(() => {
        res.send({ status: "OK"});
    });
});

app.listen(3000, () => console.log("Server running on 3000 port"));



Запускаем приложение.

npm run build-start


Для тестовых запросов мы продолжим использовать утилиту Postman

Делаем POST с тестовой записью и получаем статус- OK



Теперь убедимся, что запись появилась в базе данных. В консоли (mongo) сделаем еще один запрос:

db.posts.find()


И если все у вас прошло успешно то вы увидите - новую запись.



На этом этапе я сделал коммит и все изменения здесь - server-side-abcinblog ci -m"POST with Postman"

CRUD

GET - Обработчик для получения записей.

Выполнив метод Post.find() без параметров, мы получим все записи. И выполняем проверку ошибок в запросе. Это важно и нужно сделать везде.

//вверху файла к импортам добавим 
import Post from './models/Post';
// после post - запроса сразу
 app.get('/posts', (req, res) => {
     Post.find().then((err, posts) => {
         if(err){
             res.send(err);
         }
         res.json(posts);
     });
 });



Идем в постмен и делаем обычный GET - запос.



Теперь мы можем проверить и добавление записи, выведя потом все записи снова.

POST -запрос.



GET -запрос.



На этом этапе я сделал коммит и все изменения здесь - server-side-abcinblog ci -m"GET all posts"

DELETE - обработчик для удаления записей.

Здесь все просто. Мы будем использовать параметр id. Для удаления записи мы используем метод remove(), котоый принимает объект с параметрами. Проверяем, чтобы после выполнения запроса, запись была возвращена. Это будет говорить о том. что запись была успешно удалена из базы данных. Иначе - возвращаем ошибку.


 app.delete('/posts/:id', (req, res) => {
     PostModel.remove({
         _id: req.params.id
     }).then(post => {
         if(post) {
             res.json({ status: "deleted"});
         } else {
             res.json({ status: "error"});
         }
     })
 })



Теперь с помощью утилиты Postman отправим за сервер DELETE - запрос с id той записи, которую мы хотим удалить.

Сохраняем и перезапускаем сервер. Идем в Postman.

Я решил удалить вторую запись, поэтому скопировал ее id и подставил в адрес, сразу после слэша.



Нажал и получил:



Если сейчас выполнить GET всех постов, то получим:



Второго поста нет - он успешно удален.

На этом этапе я сделал коммит и все изменения здесь - server-side-abcinblog ci -m"DELETE post"

UPDATE - обработчик обновления записи.

Здесь мы будем использовать метод PUT, который принимает в параметрах id записи.

С помощью метода .findByIdAndUpdate() мы будем обновлять конкретную запись.

Первым параметром - id, вторым - объект со свойством set, которое хранит новые данные.

Не забываем про обработчик ошибок.


 app.put('/posts/:id', (req, res) => {
     PostModel.findByIdAndUpdate(req.params.id, {$set: req.body}, (err) => {
         if(err) {
             res.send(err);
         }
         res.json({ status: "update"});
     });
 });



Запускаем сервер и идем в Postman.

Здесь мы попытаемся обновить запись. Используем id.



Получили статус - status: "update"

Теперь, если мы опять выведем все посты методом GET, то мы увидим на месте второго поста - обновленные данные.



На этом этапе я сделал коммит и все изменения здесь - server-side-abcinblog ci -m"UPDATE post"

Контроллер.

Разработаем контроллер - класс, с помощью которого мы разделим логику запросов.

Выносим логику всех наших обработчиков запроса в отдельный файл, отдельной папки.

Для начала в папке src мы создадим папку - controllers и уже в ней создадим файл - PostController.js.

Этот класс контроллера будет содержать пять методов, каждый из которых будет выполнять свою задачу. Так как, слушатель принимает вторым параметром функцию, о мы просто скопируем их из файлов (index.js. Пример:


    update(req, res)  {
        PostModel.findByIdAndUpdate(req.params.id, {$set: req.body}, (err) => {
            if(err) {
                res.send(err);
            }
            res.json({ status: "update"});
        });
    }




Красным выделил то, что скопировали, но удалили стрелочную функцию. Дополнительно мы создадим метод read(), который будет возвращать конкретную статью по ее id.


import PostModel from '../models/Post';
import Post from '../models/Post';
class PostController {
    index(req, res) {
        Post.find().then((err, posts) => {
            if(err){
                res.send(err);
            }
            res.json(posts);
        });
    }
    create(req, res) {
        const data = req.body;
        // console.log(req.body);
        const post = new PostModel({
            title: data.title,
            text: data.text
        });

         post.save().then(() => {
            res.send({ status: "OK"});
        });
    }
    read(req, res) {
        PostModel.findOne({ _id: req.params.id}).then(post => {
            if(!post) {
                res.send({ error:"nor found"});
            } else {
                res.json(post);
            }
        })
    }
    update(req, res)  {
        PostModel.findByIdAndUpdate(req.params.id, {$set: req.body}, (err) => {
            if(err) {
                res.send(err);
            }
            res.json({ status: "update"});
        });
    }
    delete(req, res)  {
        PostModel.remove({
            _id: req.params.id
        }).then(post => {
            if(post) {
                res.json({ status: "deleted"});
            } else {
                res.json({ status: "error"});
            }
        });
    }
};

 export default PostController;



Теперь подключим его в основной файл:

import mongoose from 'mongoose';
import express from 'express';
import bodyParser from 'body-parser';
import PostController from './controllers/PostController';
const Post = new PostController();
// import PostModel from './models/Post'; перенесли в контроллер.
// import Post from './models/Post'; перенесли в контроллер.
const app = express();

mongoose.connect('mongodb://localhost/server-side');

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.get('/posts', Post.index);
app.get('/posts/:id', Post.read);
app.post('/posts', Post.create);
app.delete('/posts/:id', Post.delete);
app.put('/posts/:id', Post.update);

app.listen(3000, () => console.log("Server running on 3000 port"));



Функции запросов мы поменяли на методы из контроллера.

Напрямую обращаться к методам нельзя поэтому вверху файла мы создали экземпляр класса в отдельной переменной:

const Post = new PostController();

Теперь запускаем сервер и проверим все методы, через Postman, как мы это делали ранее.

Проверим создание поста, вывод всех постов, обновление и удаление.

На этом этапе я сделал коммит и все изменения здесь - server-side-abcinblog ci -m"Creating a controller"

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










                                                                                                                                                             

суббота, 6 октября 2018 г.

Блог на NodeJS Express MongoDB ( III ).

Добавление постов - админ панель

Начать следует с добавления постов в блог. Для этого нам потребуется некая форма, которая будет принимать информацию от пользователя, обрабатывать ее и передавать на хранение в БД.



Все материалы по ES6


Добавление формы поста

Кнопка

На сайте Materialize возьмем кнопку.

Копируем код кнопки и добавляем в самый конец файла index.html.

Все что внутри тега ul удалим вместе с тегом. Это нам не нужно. Нам достаточно одной кнопки.



По ней сложно кликнуть, ничего не происходит. Поэтому надо поставит модальное окно.

Идем на сайт Materialize и там нам нужно посмотреть как работают модальные окна.

Берем modal-trigger и добавляем к ссылке нашей кнопки и далее нам нужно указать название модального окна, которое нам нужно открыть. Мы это будем делать через data-target="название"

Модальное окно

Так же берем с сайта Materialize готовый код окна и вставляем после кнопки.

Исправляем id - оно должно совпадать с значением data-target="createForm". Название поставим - "Создать новый пост", а вместо контента поставим поля ввода.

Файл index.html полностью


<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 <!-- Compiled and minified CSS -->
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> 
 <!--Import Google Icon Font-->
 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
 <title>FS-Blog</title>
   <!--[if IE]>
   <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
 <![endif]-->
</head>
<body>
 <div class="container center" style="padding-top: 50px;">
  <div class="row">
   <div class="col s12 m8 offset-m2 lg6 offset-3" id="posts">
    <div class="preloader-wrapper small active">
     <div class="spinner-layer spinner-green-only">
      <div class="circle-clipper left">
       <div class="circle"></div>
      </div><div class="gap-patch">
       <div class="circle"></div>
      </div><div class="circle-clipper right">
       <div class="circle"></div>
      </div>
     </div>
    </div>
   </div>
  </div>
 </div>
 <!-- button -->
 <div class="fixed-action-btn">
  <a class="btn-floating btn-large red modal-trigger" data-target="createForm">
   <i class="large material-icons">add</i>
  </a>
 </div>
   <!-- Modal Structure -->
  <div id="createForm" class="modal">
   <div class="modal-content">
    <h4>Создать новый пост</h4>
    <div class="input-field">
     <input id="title" type="text" class="validate" required>
     <label for="title">Название.</label>
    </div>

    <div class="input-field">
     <textarea id="text" class="materialize-textarea"></textarea>
     <label for="text">Название.</label>
    </div>
   </div>
    <div class="modal-footer">
      <a class="waves-effect waves-light btn" id="createPost">Создать пост.</a>
    </div>
  </div>

 <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
 <script src="index.js"></script>
</body>
</html>



Теперь нужно проинициализировать модальное окно

На нужно передать в глобальный объект Материалайза элемент, который нам нужно проинициализировать и после этого все должно работать.

M.Modal.init(elems, options);

Поэтому идем в скрипт - index.js

Уберем за одно и таймаут. Он нам уже не нужен.

Инициализируем модальное окно.

M.Modal.init(document.querySelector('.modal'))

внутри события загрузки всего документа.

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

Файл index.js


const card = post => {
 return `
    <div class="card z-depth-4">
         <div class="card-content">
           <span class="card-title">${post.title}</span>
           <p>${post.text}</p>
           <small>${post.date}</small>
         </div>
         <div class="card-action">
      <button class="btn btn-small red">
       <i class="material-icons">Delete</i>
      </button> 
         </div>
       </div>
 `
}

const BASE_URL = '/api/post'
let posts =[]
let modal;
class PostApi {
 static fetch(){
  return fetch(BASE_URL, {method: 'get'}).then(res=>res.json())
 }
}

document.addEventListener('DOMContentLoaded', () => {
 PostApi.fetch().then(backendPosts => {
  posts = backendPosts.concat()
  renderPosts(posts)  
 })
 modal = M.Modal.init(document.querySelector('.modal'))
})

function renderPosts(_posts=[]) {
 const $posts = document.querySelector('#posts')
 if(_posts.length > 0) {
  $posts.innerHTML = _posts.map(post=> card(post)).join()  
 } else {
  $posts.innerHTML = `<div class="center">Постов пока нет.</div>`
 }
}



Проверяем.



Придаем функциональность

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

Внутри основного события загрузки документа в файле index.js возьмем кнопку -

document.querySelector('#createPost').addEventListener('click', onCreatePost)

Функцию мы вызываем без круглых скобок - просто передаем название.

Создаем функцию onCreatePost

В ней мы должны получит контент, который написан в инпутах. Посему, создадим переменные

$title = document.querySelector('#title')
$text = document.querySelector('#text')


Проверим, что эти поля не пустые:

if ($title.value && $text.value)

Если они не пустые, то нам нужно отправить POST-запрос, который мы реализовывали на сервере.

Для этого создадим date, которую мы хотим отправить на сервер. На сервере мы ожидаем увидеть два поля - title и text с запросом.

Поэтому мы здесь создадим новую переменную - newPost


  const newPost = {
   title: $title.value 
   text: $text.value
  }


И теперь когда у нас есть данные которые мы принимаем на сервере (объект) нам нужно отправить запрос.

Для этого у нас есть вспомогательный класс PostApi и давайте здесь же реализуем метод, который нам позволит создавать посты.

 static create(post) {

 }
Будет принимать объект поста.

Далее мы будем возвращать результат работы метода fetch(BASE_URL) по BASE_URL и далее нам необходимо передать объект конфигурации {}

Во первых мы ожидаем, что будем отправлять метод POST, поэтому


 static create(post) {
  return fetch(BASE_URL, {
   method: 'post',
  })
 }



Далее нам необходимо указать body (посмотрите на сервер - title: req.body.title, text: req.body.text, (14 и 15 строка файла routes/post.js)

Поэтому в методе fetch() мы так и пишем body и далее нам необходимо сделать строкой - сериализовать объект post

Сделаем так:

JSON.stringify(post)

Так же нам нужно здесь реализовать хедеры, чтобы сервер понимал, что работа идет с JSON-ом.


   hraders: {
    'Accept': 'application/json',
    'Content-Type':'application/json'
   }



Конфигурация запроса на этом закончена и после того, как пройдет метод fetch мф хотим получить некоторый body. Для этого в методу .then(res => res.json()) делаем то, что обычно с запросом. Отдаем некоторый json.

Метод create готов.


class PostApi {
 static fetch() {
  return fetch(BASE_URL, {method: 'get'}).then(res=>res.json())
 }
 static create(post) {
  return fetch(BASE_URL, {
   method: 'post',
   body: JSON.stringify(post),
   hraders: {
    'Accept': 'application/json',
    'Content-Type':'application/json'
   }
  }).then(res => res.json())
 }
}



Теперь у нас метод create готов и мы можем продолжить работу.

В функции onCreatePost мы сформировали новый пост и далее можем обратиться к классу PostAPI и с помощью метода create() передать ему данные - newPost и далее, когда обработается запрос и сервер нам ответит, в метод .then() мы получим новый объект post.

Нам нужно отобразить его в списке всех постов. Для этого у нас есть локальная переменная posts, гд хранятся все посты. Мы просто добавим его туда методом push()

И далее. чтобы отобразить новые посты, мы вызываем метод renderPosts(posts) c массивом posts.

Помимо того. что мы создали пост на сервере нам нужно сразу закрыть модальное окно -

modal.close()

И очистим значения инпутов -

$title.value = ''
$text.value = ''


И обновим текстовые инпуты - Обратиться к библиотеке материолайз и вызвать метод

M.updateTextFields() - по новому отобразит текстовые инпуты.

Сейчас при попытке создания посты мы получим следующее сообщение:



Для правильной работы нашего сервера нам нужно добавить плагин, который будет за нас парсить входящие данные. Мы с клиента их сериализовали - превратили в строку и теперь нужно сделать так, чтобы сервер их принимал как обычный JS-объект.

Для этого нам потребуется пакет body-parser

Остановим выполнение сервера.

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

npm install body-parser


Подключаем его в папке app.js где-нибудь вверху.

const bodyParser = require('body-parser')

Теперь нужно применить пакет к нашему серверу.

Например. пред тем, как мы определяем роуты мы можем написать такую конструкцию:

app.use(bodyParser.json())

Будет преобразовывать входящие параметры в json-объект.

app.js


const express = require('express')
const mongoose = require('mongoose')
const bodyParser = require('body-parser')
const path = require('path')
const postRouter = require('./routes/post')
const keys = require('./keys')

const port = process.env.PORT || 5000
const clientPath = path.join(__dirname, 'client')

mongoose.connect(keys.mongoURI)
   .then(() => console.log('MongoDB connected'))
   .catch(err => console.error(err)) 
const app = express()
app.use(bodyParser.json())
app.use('/api/post', postRouter)
app.use(express.static(clientPath))
app.listen(port,()=>{
 console.log(`Server run on ${port} port`)
})



Запускаем сервер

Добавляем пост:



Если обновить страницу, то мы все равно увиим данный пост, потому что он берется уже из БД. В консоли можно посмотреть данные поста



Если посмотреть в mLab в коллекциях то мы увидим данные нашего поста.



Теперь можно добавлять посты на страницу.

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

Реализация удаления поста.

Нас интересует клиентский скрипт - index.js

Здесь есть функция, которая вызывается когда полностью загрузится контент DOM. В ней у нас уже есть прослушка событий на кнопке "Добавить пост". Создадим еще одну, которая позволит нам удалять определенные посты. В данном случае мы будем делегировать события и прослушку повесим на весь cписок постов (id="posts"), потому что на кнопку удаления повесить ее мы не можем, так как они все время меняются. Поэтому лучше делегировать события.

document.querySelector('#posts').addEventListener('click', onDeletePost)

и создаем данную функцию внизу файла.

В нее принимаем некоторый event

Теперь нам нужно определить, что нам нужно что-то удалить. Так как сейчас событие event будет срабатывать при клике по любому месту поста.

Нам нужно понять, что клик был именно по кнопке удаления поста.

Для этого мы можем в функции, которая генерирует нам html-карточки добавить на кнопку удаления поста специальный класс, напрмер .js-remove

Теперь нужно в функции определить, есть ли у элемента по которому мы кликнули этот класс.

В конструкцию if запишем: если у event.target в объекте classList есть класс .js-remove, то мы что-то делаем.

if (event.target.classList.contains('js-remove'))

далее спросим у пользователя через функцию confirm желает ли он удалить этот пост.

const decision = confirm('Вы уверены, что хотите удалить пост?')

А далее если пользователь нажал (согласился), то нужно понять какой именно пост нужно удалить.

Нам для этого нужно получить id поста. id хранится в объекте post._id. И зная что мы будем кликать именно по кнопке удаления, мы можем на кнопку добавить атрибут - data-id="${post._id}" и будем забирать именно этот атрибут.

const id = event.target.getAttribute('data-id')

Далее, зная нужный id мы можем отправить его на сервер.

Для этого нам нужно реализовать функцию удаления в классе PostApi.

Там мы создадим новую функцию static remove(id) в нее мы будем получать id и все что мы будем делать - возвращать функцию fetch() по BASE_URL но к нему надо еще добавить id, поэтому В параметрах метод delete, потому что мы будем слушать именно его. и после этого получим результат.


 static remove(id) {
   return fetch(`${BASE_URL}/${id}`, {
    method: 'delete'
   }).then(res => res.json())
 }



Теперь мы можем обратиться к классе PostApi взывать у него метод remove передать ему нужный id и как только сервер ответи успехом, мы в функции .then() удалим пост из списков (визуально)


function onDeletePost(event) {
 if (event.target.classList.contains('js-remove')) {
  const decision = confirm('Вы уверены, что хотите удалить пост?')

  if (decision) {
   const id = event.target.getAttribute('data-id')

   PostApi.remove(id).then(() => {
    const postIndex = post.findIndex(post => post._id === id)
    posts.splice(postIndex, 1)
    renderPosts(posts)
   })
  }
 }
}



Правильно указать дату поста и мультимтрочный текст

Текст.
Параграфу просто добавить инлайн стиль white-space:pre-line



Для нормального отображения даты нужно поле ${post.date} обернуть в конструктор ${new Date(post.date).toLocaleDateString()}

Метод приводит даты к локальному формату.

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





                                                                                                                                                             

четверг, 4 октября 2018 г.

Блог на NodeJS Express MongoDB ( II ).





Все материалы по ES6


Создание REST API роутов

В корне проекта создадим папку - routes.

Внутри нее создадим файл, который будет отвечать за роуты к постам - post.js

В файле сразу подключаем express - const express = require('express')

Далее создадим переменную, которая добывается из функции экспресс - Router()

const router = express.Router()

С этой переменной мы будем работать в этом файле и в конце экспортировать на ружу.

module.exports = router

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

Для этого мы должны перейти в файл app.js, где мы описываем сервер и подключит данный роутер.

const postRouter = require('./routes/post.js').


const express = require('express')
const mongoose = require('mongoose')
const path = require('path')
const postRouter = require('./routes/post')
const keys = require('./keys')

const port = process.env.PORT || 5000
const clientPath = path.join(__dirname, 'client')

mongoose.connect(keys.mongoURI)
			.then(() => console.log('MongoDB connected'))
			.catch(err => console.error(err))	
const app = express()
app.use(express.static(clientPath))
app.listen(port,()=>{
	console.log(`Server run on ${port} port`)
})



Теперь, для того чтобы использовать наш роутер, мы должны, после того, как определили объект нашего приложения =>const app = express() (ниже) написать команду

app.use('', postRouter)

Для того чтобы применить какой то роут и экспресc понимал ссылки.

Первым параметром мы можем указать url- префикс, который будет доступен для данного роута.

Вторым- тот роутер, который нам нужно использовать.

Теперь нужно подумать какой префикс нам здесь написать?

По сути, у нас будет часть сервера, которая будет отдавать клиентскую сторону - index.html и у нас будет часть сервера, которая будет отвечать за REST API. Соответственно, все что будет делаться через REST API мы можем начинать с префикса /api/ и далее, так как у нас идет здесь идет работа именно с постами, мы можем указывать сущьности - например post

app.use('/api/post', postRouter)

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

Перейдем в файл post.js

По сути для нашего приложения важно реализовать три действия:
  1. - получит все посты, которые есть в БД
  2. - создать новый пост
  3. - реализовать удаление какого-то поста
В REST API для того, чтобы получать что-то мы используем http-запросы с методом get.

Для того, чтобы реализовать метод get в экспрессе мы обращаемся к роутеру (router) и вызываем у него метод get.

Здесь в качестве параметра мы просто указываем слэш - '/'.

Вторым параметром указываем callback-функцию, которая принимает два параметра ( req -request - запрос res - response - ответ.)

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


router.get('', (req, res) => {
	
})



Теперь давайте посмотрим. что это будет за роут и как он будет определяться.

У нас есть некоторый префикс - '/api/post' и это означает, что мы будем видеть запрос следующего характера:

http://localhost:5000/api/post

который будет реагировать на тип GET. И в случае, если экспресс увидит, что это запрос GET, то будет нам что-то отдавать.

Похожая ситуация у нас будет реализована еще для двух методов.

В REST API если мы хотим что-то создать, то мы используем метод POST, удалить - DELETE.

По сути, все запросы будут идти по одной и той же ссылке, но Экспресс будет понимать, что надо делать.

routes/post.js


const express = require('express')

const router = express.Router()
// http://localhost:5000/api/post GET
router.get('', (req, res) => {

})
// http://localhost:5000/api/post POST
router.post('', (req, res) => {
	
})
// http://localhost:5000/api/post DELETE
router.delete('', (req, res) => {
	
})

module.exports = router



Сейчас, давайте попробуем описать метод GET с помощью которого мы хотим что-то получить.

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

const Post = require('../models/Post')

Теперь с помощью этого объекта мы уже можем работать с БД.

Для того, чтобы получить все сущьности из БД, нам нужно в методе GETобратиться к и вызвать у него метод find(), передав в метод пустой объект. Это означает, что мы будем требовать все посты без каких либо условий.


router.get('', (req, res) => {
	Post.find({})
})



Данная операция является асинхронной. Она нам будет возвращать некий промис мы его можем ченить с помощью метода .then и после этого как-то обрабатывать.


router.get('', (req, res) => {
	Post.find({}).then((posts)=>{
 	//что-то делать...	
 })
})



Мы пойдем более современным путем. Так как мы работает на платформе NodeJS, которая не требует понимания разных браузеров, то здесь можно использовать все новшества современного JS ES6.

Поэтому мы смело можем использовать синтаксис - async/await.

Вместо того, чтоб так ченить промисы.

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

И теперь, чтобы получить список этих постов, мы можем просто создать переменную posts и с помощью префикса await - подождать.

const posts = await Post.find({})

Эта конструкция полностью идентична

	Post.find({}).then((posts)=>{
          // ...
	})



При этом мы написали полностью асинхронный код и у нас нет вложенных функций.

Таким образом мы получили:


const express = require('express')

const router = express.Router()

const Post = require('../models/Post')
// http://localhost:5000/api/post GET
router.get('', async(req, res) => {
const posts = await Post.find({})
})
// http://localhost:5000/api/post POST
router.post('', (req, res) => {
	
})
// http://localhost:5000/api/post DELETE
router.delete('', (req, res) => {
	
})

module.exports = router



Думаем над тем. что нам делать еще в методе GET По сути, мы должны что-то отдать клиенту (список постов). Для того, чтобы ответить клиенту мы можем воспользоваться объектом res, затем указать статус

res.status(200) и с помощью метода json указать те данные, которые мы хотим отправить обратно - posts.

res.status(200).json(posts)


const express = require('express')

const router = express.Router()

const Post = require('../models/Post')
// http://localhost:5000/api/post GET
router.get('', async(req, res) => {
	const posts = await Post.find({})
	res.status(200).json(posts)
})
// http://localhost:5000/api/post POST
router.post('', (req, res) => {
	
})
// http://localhost:5000/api/post DELETE
router.delete('', (req, res) => {
	
})

module.exports = router



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

Метод POST

Здесь мы должны сформировать новый пост в зависимости от тех данных, которые к нам придут.

Создадим переменную postData и это будет объект с полями title и text. Здесь мы будем с клиента отправлять данные в БД. Для сбора данных и нужны эти поля.

Теперь эти данные нужно обработать. Это сделать просто. У нас есть объект req с помощью которого будет сделан запрос и сказать ему, что например, title хранится в req.body.title.


router.post('', (req, res) => {
	const postData = {
		title: req.body.title,
		text: req.body.text,
	}
})



Теперь у нас есть данные необходимые для создания нового поста в базе и мы можем создать новый пост с помощью модели Post на основе данных postData.

const post = new Post(postData)

И после этого нам нужно сохранить это все в БД. Для этого можно просто написать:

await post.save()

И сделать функцию асинхронной:


router.post('', async (req, res) => {
	const postData = {
		title: req.body.title,
		text: req.body.text,
	}
	const post = new Post(postData)
	await post.save()
})



Этой строкой -

await post.save()

мы будем ждать, когда код сохранится на сервере. Далее объект post изменится, потому что придет ответ с БД и у нас будет уникальный ID, который нам выдаст MongoDB и у нас определится дата. когда был создан пост..

И теперь мы можем ответить (res)клиенту оперируя теми данными, которые мы получили с БД.

В RESTAPI если мы что-то создаем, то используем статус 201, а далее методом json мы указываем. чо будем обратно возвращать post.

res.status(201).json(post)


// http://localhost:5000/api/post POST
router.post('', async (req, res) => {
	const postData = {
		title: req.body.title,
		text: req.body.text,
	}
	const post = new Post(postData)
	await post.save()
	res.status(201).json(post)
})



Метод DELETE

Он будет тоже асинхронным, как и предыдущие методы. (Добавим async).

В этом методе нам нужно будет удалить пост по ID. Поэтому здесь уже не подойдет такой url - http://localhost:5000/api/post, потому что нам в нем надо передать id того поста, который мы хотим удалить. Например id =34;

http://localhost:5000/api/post/34

Эта часть будет динамическая.

Для того, чтобы указать какую-либо динамическую часть в Экспрессе, нам достаточно поставить после слэша двоеточие и написать id.

Именно так - id будет называться параметр, который мы будем получать в этом месте.

Теперь нам нужно с помощью модели Post вызвать метод remove(), который позволяет нам что-то удалять из базы. и далее мы говорим условия - какой именно пост мы хотим удалить в объекте. Мы хотим удалить пост у которого _id === id который придет в роуте. А этот параметр хронися в объекте запроса req (req.params.is)

Так как функция асинхронная, то мы ее должны подождать - await


router.delete('/:id', async (req, res) => {
	await Post.remove({_id: req.params.id})
})



Как только удалится нужный нам пост мы можем ответить клиенту статусом 200 и в json отправить мессендж -"Удалено"


router.delete('/:id', async (req, res) => {
	await Post.remove({_id: req.params.id})
	 res.status(200).json({
	 	message: 'Удалено'
	 })
})



Теперь наш роутер, который работает с сущностями поста полностью готов.

Файл post.js


const express = require('express')

const router = express.Router()

const Post = require('../models/Post')
// http://localhost:5000/api/post GET
router.get('/', async (req, res) => {
	const posts = await Post.find({})
	res.status(200).json(posts)
})
// http://localhost:5000/api/post POST
router.post('/', async (req, res) => {
	const postData = {
		title: req.body.title,
		text: req.body.text,
	}
	const post = new Post(postData)
	await post.save()
	res.status(201).json(post)
})
// http://localhost:5000/api/post DELETE
router.delete('/:id', async (req, res) => {
	await Post.remove({_id: req.params.id})
	 res.status(200).json({
	 	message: 'Удалено'
	 })
})

module.exports = router



Далее перейдем к тестированию, создав некую клиентскую часть.

Тестирование серверной части.

Всё, что относится к фронтенд части хранится в папке client. Там, пока что, один файл - index.html

Начнем с верстки. На ней я подробно останавливаться не буду. Отмечу только, что для адаптивности и простоты я использовал фреймворк Materialize.

Подключаем css, material icons и скрипт js к нашему index.html файлу через CDN (ссылки на сайте) . После скрипта материалайза подключаем наш скрипт index.js, который мы создадим в той же папке.

В нем мы будем вести разработку на нативном JS.

В файле index.html создадим div - контейнер, которому зададим стиль, чтобы он не прилипал к верху экрана и в него мы будем выводить посты.


<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
	 <!-- Compiled and minified CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">	
  <!--Import Google Icon Font-->
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
	<title>FS-Blog</title>
   <!--[if IE]>
   <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
 <![endif]-->
</head>
<body>
	<div class="container center" style="padding-top: 50px;">
		<div class="row">
			<div class="col s12 m8 offset-m2 lg6 offset-3" id="post">
				<div class="card">
	        <div class="card-content">
	          <span class="card-title">Post Title</span>
	          <p>I am a very simple card. I am good at containing small bits of information.</p>
	          <small>12.12.1212</small>
	        </div>
	        <div class="card-action">
						<button class="btn btn-small red">
							<i class="material-icons">Delete</i>
						</button>	
	        </div>
	      </div>
			</div>
		</div>
	</div>


	<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
	<script src="index.js"></script>
</body>
</html>



Запускаем сервер

npm run dev


Переходим на сайт localhost:5000 и видим:



Теперь перейдем к клиентскому скрипту и посмотрим как вывести список всех постов.

Первое - нам нужно сделать некий компонент, который будет генерировать html-код определенного поста. Поэтому, все что отмечено красным вырезаем и переносим в файл index.js


<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
	 <!-- Compiled and minified CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">	
  <!--Import Google Icon Font-->
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
	<title>FS-Blog</title>
   <!--[if IE]>
   <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
 <![endif]-->
</head>
<body>
	<div class="container center" style="padding-top: 50px;">
		<div class="row">
			<div class="col s12 m8 offset-m2 lg6 offset-3" id="post">
		<div class="card z-depth-4">
	        <div class="card-content">
	          <span class="card-title">Post Title</span>
	          <p>I am a very simple card. I am good at containing small bits of information.</p>
	          <small>12.12.1212</small>
	        </div>
	        <div class="card-action">
						<button class="btn btn-small red">
							<i class="material-icons">Delete</i>
						</button>	
	        </div>
	      </div>
			</div>
		</div>
	</div>


	<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
	<script src="index.js"></script>
</body>
</html>



И так как здесь у нас нативный JS, то мы создадим подобие компонента в переменной card


const card = post => {
	return `
		<div class="card z-depth-4">
	           <div class="card-content">
	            <span class="card-title">${post.title}</span>
	            <p>${post.text}</p>
	            <small>${post.date}</small>
	           </div>
	          <div class="card-action">
		    <button class="btn btn-small red">
			<i class="material-icons">Delete</i>
		    </button>	
	        </div>
	      </div>
	`
}



Функция, которая принимает post и отдает html разметку. Данные в которой мы выводим как переменные.

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

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

Так как мы сейчас работаем с постами, то мы можем создать локальный класс, который будет отвечать за запросы к бэкенду.

Допустим, что это будет класс PostApi. У него будет несколько методов.

Первый статический static fetch(), в который мы не будем передавать параметры. Этим методом мы будем делать запрос на получение всех наших постов.

В JS есть нативная функция fetch(), с помощью нее мы как раз и можем делать запросы.

Первым параметром в эту функцию нам надо передать url по которому мы хотим сделать запрос.

Для этого мы создадим новую константу - const BASE_URL='api/post' - Это та самая строчка. которая обозначена в файле app.js.

Вторым параметром мы указываем метод с помощью которого мы хотим сделать запрос. В нашем случае, когда мы хотим получить список всех потов это метод GET. ({method: get}.

Далее, метод fetch() возвращает промис и в методе .then мы можем привести тот формат который прилетает к нам с сервера и обрабатывается JS, привести в понятный формат объектов. Поэтому - res=>res.json() и все это вернем из метода fetch().


const BASE_URL = '/api/post'

clss PostApi {
	static fetch(){
		return fetch(BASE_URL, {method: get}).then(res=>res.json())
	}
}



И теперь в какой момент нам необходимо сделать запрос?

При загрузке страницы.

На документ поставим обработчик события.

document.addEventListener('DOMContentLoaded', () => {
	
})


Как только запускаем приложение нам нужно сделать запрос к бекенду, поэтому мы обращаемся к PostApi.fetch и вызываем метод .fetch() и так как он возвращает промис, то .then(). В этом методу мы будем получать объект backendPosts. То есть это будет уже тот массив, который прилетит от сервера. И теперь, нам нужно просто вывести все эти посты в html.

Для этого создадим наверху файла переменную let posts = []. Именно в ней мы будем хранить все посты, которые у нас есть.

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

Он просто сгенерирует дубликат данного массива.


const BASE_URL = '/api/post'
let posts =[]
clss PostApi {
	static fetch(){
		return fetch(BASE_URL, {method: 'get'}).then(res=>res.json())
	}
}

document.addEventListener('DOMContentLoaded', () => {
	PostApi.fetch().then(backendPosts => {
		posts = backendPosts.concat()
	})
})



Теперь, когда у нас есть список всех постов с бэкенда и нам остается их отрендерить.

Для этого создадим функцию renderPosts(posts) куда и передадим эти посты.

Функцию опишем ниже. По умолчанию она будет принимать пустой массив - _posts=[]

У нас есть id="posts" в файле index.html куда нам нужно выводить наши посты.

$ - просто обозначает, что это некоторый элемент.


function renderPosts(_posts=[]) {
	const $posts = document.querySelector('#posts')

}



Далее, проверим. Если длина массива пришедшего в функция больше 0, то есть у нас есть некоторые посты, то мы будем генерировать карточки. Иначе мы просто выведем сообщение "Постов пока нет".

Если посты есть, то мы должны сгенерировать html. Нам нужно пробежаться по массиву постов и сформировать из каждого карточку с нужным html.

Мы можем обратиться к локальной переменной _posts и далее с помощью метода .map() мы на каждой итерации будем получать определенный post (это объект) и на выходе вернуть html, то есть вызвать функцию card(post)

Преобразуем массив к строке методом .join(' '). Через пробел.


function renderPosts(_posts=[]) {
	const $posts = document.querySelector('#posts')
	if(_posts.length > 0) {
		$posts.innerHTML = _posts.map(post=> card(post)).join()	 
	} else {
		$posts.innerHTML = `
Постов пока нет.
` } }


Сохраняем и обновляем страницу Идем на вкладку Networkв в консоли.



Видим, что "Постов пока нет", а в запросах - url совпадает и метод GET, и получаем пустой массив post



С выводом постов мы разобрались.

Единственное что мы можем добавить, это индикатор загрузки.

Имитируем задержку сервера функцией setTimeout пред рендерингом постов, а на это время сделаем показ индикатора загрузки.


document.addEventListener('DOMContentLoaded', () => {
	PostApi.fetch().then(backendPosts => {
		posts = backendPosts.concat()
		setTimeout(()=>{
		renderPosts(posts)
		}, 2000)			
	})
})



Прелоудер возьмем на сайте materializecss.com

Скопируем там готовый код прелоудера и добавим его в наш див поста в файле index.html

Файл index.html полностью:


<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
	 <!-- Compiled and minified CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">	
  <!--Import Google Icon Font-->
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
	<title>FS-Blog</title>
   <!--[if IE]>
   <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
 <![endif]-->
</head>
<body>
	<div class="container center" style="padding-top: 50px;">
		<div class="row">
			<div class="col s12 m8 offset-m2 lg6 offset-3" id="posts">
				<div class="preloader-wrapper small active">
					<div class="spinner-layer spinner-green-only">
						<div class="circle-clipper left">
							<div class="circle"></div>
						</div><div class="gap-patch">
							<div class="circle"></div>
						</div><div class="circle-clipper right">
							<div class="circle"></div>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div>


	<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
	<script src="index.js"></script>
</body>
</html>



Файл index.js полностью:



const card = post => { return ` <div class="card z-depth-4"> <div class="card-content"> <span class="card-title">${post.title}</span> <p>${post.text}</p> <small>${post.date}</small> </div> <div class="card-action"> <button class="btn btn-small red"> <i class="material-icons">Delete</i> </button> </div> </div> ` } const BASE_URL = '/api/post' let posts =[] class PostApi { static fetch(){ return fetch(BASE_URL, {method: 'get'}).then(res=>res.json()) } } document.addEventListener('DOMContentLoaded', () => { PostApi.fetch().then(backendPosts => { posts = backendPosts.concat() setTimeout(()=>{ renderPosts(posts) }, 2000) }) }) function renderPosts(_posts=[]) { const $posts = document.querySelector('#posts') if(_posts.length > 0) { $posts.innerHTML = _posts.map(post=> card(post)).join() } else { $posts.innerHTML = `<div class="center">Постов пока нет.</div>` } }


Теперь перед загрузкой постов мы будем видеть индикатор - вращающиеся полуокружность и через 2 сек. сообщение "Постов пока нет".





                                                                                                                                                             


Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.Пишите мне - kolesnikovy70 почта gmail.com