Недавно меня спросили, как фронтенд-разработчику проще всего сохранить пользовательские данные? Под катом — моя краткая инструкция для тех, кто с базами данных «на вы».

Настройка базы данных


Во-первых, нам нужна актуальная база данных. Можете сделать себе бесплатную на mlab. Когда зарегистрируетесь, во вкладке MongoDB Deployments кликните на create new. БД-песочница предоставляется бесплатно, ею мы и воспользуемся.

После создания базы данных нужно создать аккаунт для нашей аутентификации. Кликните на имя БД, затем users, потом add database user. Сохраните где-нибудь логин/пароль, они нам ещё понадобятся.

В верхней части страницы БД вы увидите URI MongoDB. Это веб-адрес нашей базы. URI — это как адрес веб-страницы. Формат URI для MongoDB URI будет такой:

mongodb://<dbuser>:<dbpassword>@<host>:<port>/<dbname>

Вот URI моей базы данных:

mongodb://admin:superSecretPassword@ds111885.mlab.com:11885/medium

Настройка сервера


В качестве бэкенда возьмём Node. Чтобы его не настраивать, можете просто кликнуть тут и клонировать мой проект на Glitch.

Взгляните на мой начальный файл server.js:

// init project
const express = require('express'); // the library we will use to handle requests
const app = express(); // instantiate express
app.use(require("cors")()) // allow Cross-domain requests 
app.use(require('body-parser').json()) // automatically parses request data to JSON

// base route
app.get("/", function (request, response) {
  response.send("TODO") // always responds with the string "TODO"
});

// base route
app.post("/", function (request, response) {
  response.send("TODO") // always responds with the string "TODO"
});

app.put("/", function (request, response) {
  response.send("TODO") // always responds with the string "TODO"
});


// listen for requests, the process.env.PORT is needed because
// we are using glitch, otherwise you could have written 80 or whatever
var listener = app.listen(process.env.PORT, function () {
  console.log('Your app is listening on port ' + listener.address().port);
});

Начинаем с импортирования express?—?это библиотека, которую мы будем использовать для обработки запросов к серверу.

Для междоменных запросов нам понадобится use(require(cors)). Это запросы от веб-сайта, который хостится в одном домене, к серверу в другом домене.

Команда app.use(require('body-parser').json()) автоматически парсит для нас запросы в JSON.

Затем мы передаём методу get маршрут, который хотим обработать, и саму обрабатывающую коллбэк-функцию. Когда кто-нибудь откроет страницу / нашего сайта, это запрос будет обработан коллбэком. Базовый домен у нас будет относительным, так что если у вас адрес сайта shiny-koala.glitch.com, то маршрут /about превратится в shiny-koala.glitch.com/about.

Поясню: под «открыть страницу» я подразумеваю запрос, использующий на вашем сервере метод GET. HTTP-методы — это лишь типы запросов, которые вы можете адресовать серверу. Мы будем использовать только такие:

  • GET — этот метод используется для извлечения ресурсов с сервера. Например, если вы открываете Facebook, то при этом скачиваются все необходимые HTML, CSS и JavaScript.
  • POST — этот метод используется для создания ресурсов на сервере. Например, когда вы что-то пишете на Facebook, то написанное вами отправляется на серверы соцсети в POST-запросе.
  • PUT — этот метод используется для обновления ресурсов на сервере. Например, когда вы редактируете свой пост, все ваши правки отправляются на сервер Facebook в PUT-запросе.

app.post и app.put работают так же, как app.get, только обрабатывают POST- и PUT- методы вместо GET.

Маршрутизация


Раз вы поднимаете сервер, вам понадобится его протестировать. Для прогона HTTP-запросов можете использовать удобный сайт REST test test или приложение Insomnia.

Для проверки URL своего Glitch-приложения кликните на кнопку show.

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

Например: /ZaninAndrea или /JohnGreen.

Возникла трудность: пожалуй, мы не можем прописать в коде все пути, это не слишком масштабируемый подход. Нам нужны параметры маршрутизации. А в коде мы пропишем лишь один путь: /:user

Двоеточие говорит Express ловить любой путь, начинающийся со слеша / и далее состоящий из букв и цифр.

Например:

  • /ZaninAndrea будет пойман.
  • /Johnny45 будет пойман.
  • /alex/score не будет пойман.

Затем можно вернуть значение user переменной request.params.user

// base route
app.get("/:user", function (request, response) {
  response.send(request.params.user) 
});

// base route
app.post("/:user", function (request, response) {
  response.send(request.params.user) 
});

// base route
app.put("/:user", function (request, response) {
  response.send(request.params.user) 
});

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

Добавление информации в БД


Мы знаем, кто у нас пользователь, и теперь нужно сохранить о нём какую-нибудь информацию.
Для обращения к базе данных мы воспользуемся библиотекой mongodb. Её можно установить двумя способами:

npm install mongodb --save

либо, если вы используете Glitch, откройте файл package.json и кликните на кнопку Add package.

Давайте загрузим библиотеку и сохраним URI MongoDB в переменной:

const mongodb = require('mongodb'); // load mongodb
const uri = process.env.URI;

URI — очень важная информация, это всё, что нам нужно для обращения к базе данных. Лучше всего положить URI в файл .env, который невидим для других.

URI=mongodb://admin:PASSWORD@ds111885.mlab.com:11885/medium

Glitch автоматически подгрузит переменные из файла .env в переменную process.env.

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

mongodb.MongoClient.connect(uri, function(err, db) {
  // base route
  app.get("/:user", function (request, response) {
    response.send(request.params.user) 
  });

  // base route
  app.post("/:user", function (request, response) {
    response.send(request.params.user) 
  });
  
  // base route
  app.put("/:user", function (request, response) {
    response.send(request.params.user) 
  });
  
  // listen for requests, the process.env.PORT is needed because
  // we are using glitch, otherwise you could have written 80 or whatever
  var listener = app.listen(process.env.PORT, function () {
    console.log('Your app is listening on port ' + listener.address().port);
  });
})

Базы данных организованы в виде коллекций, а коллекции содержат документы (JSON-файлы). Давайте подключимся к коллекции user (она будет создана при первом к ней обращении).

mongodb.MongoClient.connect(uri, function(err, db) {
  const collection = db.collection('users')
  // ...
}

Во-первых, нам нужно обработать путь POST. Его мы будем использовать при первом добавлении данных о пользователе. Затем мы воспользуемся путём PUT для обновления данных.

  app.post("/:user", function (request, response) {
    // inserts a new document on the server
    collection.insertOne({ ...request.body, user : request.params.user }, function (err, r) {
      if (err){
        response.send("An error occured") 
      }else{
        response.send("All well")
      }
    })
  });

Метод collection.insertOne добавляет в коллекцию новый документ. В нашем случае у каждого пользователя свой собственный документ.

{ ...request.body, user : request.params.user } 

использует spread-оператор для объединения данных, предоставленных в теле запроса, а также переданных пользователем по URL.

И тогда получается документ, хранящийся в коллекции.

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

Получение информации из базы данных


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

app.get("/:user", function (request, response) {
  collection.find({ user : request.params.user }).toArray(function (err, docs) {
    if (err){
      response.send("An error occured") 
    }else{
      response.send(docs)
    }
  })
});

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

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

Обновление информации в базе данных


Для обновления информации об уже существующем пользователе воспользуемся методом PUT.

// base route
  app.put("/:user", function (request, response) {
    collection.updateOne({ user : request.params.user },
                         {$set:{ ...request.body, user : request.params.user }},
                         function (err, r) {
      if (err){
        response.send("An error occured") 
      }else{
        response.send("All well")
      }
    })
  });

Первый аргумент — это фильтр, вроде того, что мы использовали в методе GET.

Второй аргумент?—?документ обновления (update document), подробнее о нём можно почитать здесь. В данном случае мы говорим базе данных объединить информацию, переданную пользователем, с уже существующей информацией.

Но будьте осторожны, потому что вложенные параметры будут заменены, а не объединены.

Напоследок


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

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

Комментарии (5)


  1. MasMaX
    30.11.2017 15:33

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


  1. inoyakaigor
    30.11.2017 17:02

    А можно заюзать старый добрый MySQL + Sequelize


  1. hazard2
    30.11.2017 17:34

    Вообще использовать nosql только потому что ее просто настраивать — это как-то странно.


  1. tehSLy
    01.12.2017 11:04

    У меня возник только один вопрос: зачем фронтэнд-разработчику настраивать базу данных?


    1. VolCh
      01.12.2017 11:40

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