Недавно меня спросили, как фронтенд-разработчику проще всего сохранить пользовательские данные? Под катом — моя краткая инструкция для тех, кто с базами данных «на вы».
Настройка базы данных
Во-первых, нам нужна актуальная база данных. Можете сделать себе бесплатную на 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), подробнее о нём можно почитать здесь. В данном случае мы говорим базе данных объединить информацию, переданную пользователем, с уже существующей информацией.
Но будьте осторожны, потому что вложенные параметры будут заменены, а не объединены.
Напоследок
Это далеко не исчерпывающее руководство по базам данных и программированию бэкенда, но вполне достаточное для того, чтобы вы могли запустить свой личный проект.
Возможно, в будущем я напишу статью об аутентификации, а пока не храните в своей базе данных конфиденциальную информацию.
Автор: Raiffeisenbank