company_banner

Node.js, Express и MongoDB: API за полчаса

Original author: Scott Domes
  • Translation
Начинающему программисту разработка для Node.js может показаться сущим кошмаром. Виной всему – гибкость этой платформы и отсутствие чётких руководств. Но, на самом деле, всё не так уж и страшно.


Вот, например, типичная задача: разработка REST API, серверной части некоего приложения. Обилие собственных возможностей Node и множество дополнительных модулей, которые способны помочь в решении этой задачи, способны завести новичка в тупик, вызванный богатством выбора. Основные вопросы здесь заключаются в подборе компонентов и в настройке их совместной работы.

Один из способов создания серверной части приложения заключается в применении связки из Node.js, фреймворка Express и СУБД MongoDB. Собственно говоря, сегодня я расскажу о том, как создать рабочий макет API, который может служить основой для практически любого приложения. Здесь мы реализуем основные маршруты REST, будем взаимодействовать с API по HTTP и использовать простые варианты работы с базой данных.

Для того, чтобы успешно освоить этот материал, вам надо понимать, что такое REST API, иметь представление об операциях CRUD и обладать базовыми знаниями в области JavaScript. Здесь я использую ES6, ничего особенно сложного, в основном – стрелочные функции.

Мы разработаем скелет серверной части приложения для создания заметок, похожего на Google Keep. При этом с заметками можно будет выполнять все четыре CRUD-действия, а именно – создание (create), чтение (read), обновление (update), и удаление (delete).

Предварительная подготовка


Если Node у вас пока нет, самое время его установить. После установки создайте папку и выполните в ней команду инициализации нового проекта:

npm init

В ходе инициализации ответьте на вопросы, в частности, дайте приложению имя «notable» (или, если хотите, любое другое).

Теперь в папке должен появиться файл package.json. Это означает, что можно начать устанавливать дополнительные пакеты, от которых зависит проект.

В качестве фреймворка мы планируем использовать Express. Системой управления базами данных будет MongoDB. Кроме того, в качестве вспомогательного средства для работы с JSON, используем пакет body-parser. Установим всё это:

npm install --save express mongodb body-parser

Ещё, я очень рекомендую установить Nodemon как dev-зависимость. Это простой маленький пакет, который, при изменении файлов, автоматически перезапускает сервер.

Для установки этого пакета выполните команду:

npm install --save-dev nodemon

Затем можно добавить следующий скрипт в файл package.json:

// package.json
  "scripts": {
    "dev": "nodemon server.js"
  },

Готовый package.json будет выглядеть примерно так:

// package.json
{
  "name": "notable",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "dev": "nodemon server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.15.2",
    "express": "^4.14.0",
    "mongodb": "^2.2.16"
  },
  "devDependencies": {
    "nodemon": "^1.11.0"
  }
}

Теперь создадим файл server.js и приступим к работе над API.

Сервер


Начнём с подключения зависимостей в файле server.js.

// server.js
const express        = require('express');
const MongoClient    = require('mongodb').MongoClient;
const bodyParser     = require('body-parser');
const app            = express();

MongoClient будем использовать для взаимодействия с базой данных. Кроме того, здесь мы инициализируем константу app, символизирующую наше приложение, экземпляром фреймворка Express. Чтобы сервер заработал, осталось лишь указать приложению на то, чтобы оно начало прослушивать HTTP-запросы.

Тут укажем порт и запустим прослушивание следующим образом:

// server.js
const port = 8000;
app.listen(port, () => {
  console.log('We are live on ' + port);
});

Теперь, если выполнить команду npm run dev (или – node server.js, если вы не устанавливали Nodemon), в терминале должно появиться сообщение: «We are live on port 8000».

Итак, сервер работает. Но сейчас он не делает совершенно ничего полезного. Давайте с этим разберёмся.

Маршруты, ориентированные на CRUD-операции


Мы планируем создать 4 маршрута. А именно:

  • CREATE – создание заметок.
  • READ –чтение заметок.
  • UPDATE –обновление заметок.
  • DELETE –удаление заметок.

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

Для того, чтобы протестировать API, понадобится нечто, способное имитировать запросы клиентской части приложения. Решить эту задачу нам поможет отличная программа, которая называется Postman. Она позволяет выполнять простые HTTP-запросы с заданным телом и параметрами.

Установите Postman. Теперь всё готово к настройке маршрутов.

О структуре проекта


В большинстве руководств по Node.js (и во множестве реальных приложений) все маршруты размещают в одном большом файле route.js. Мне такой подход не очень нравится. Если разложить файлы по разным папкам, это улучшит читаемость кода, приложением будет легче управлять.

Наше приложение большим не назовёшь, но предлагаю сделать всё как надо, учитывая, всё же, его скромные масштабы. Создайте следующие папки: папку app, а внутри неё – routes. В папке routes создайте файлы index.js и note_routes.js. Другими словами, структура проекта будет выглядеть так: root > app > routes > index.js и note_routes.js.

mkdir app
cd app
mkdir routes
cd routes
touch index.js
touch note_routes.js

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

Создание заметок: маршрут CREATE


Начнём с маршрута CREATE. Для этого ответим на вопрос: «Как создать заметку?».
Прежде чем приступить к созданию заметок, нам понадобится расширить инфраструктуру приложения. В Express маршруты оборачивают в функцию, которая принимает экземпляр Express и базу данных как аргументы.

Выглядеть это может так:

// routes/note_routes.js
module.exports = function(app, db) {
};

Теперь можно экспортировать эту функцию через index.js:

// routes/index.js
const noteRoutes = require('./note_routes');
module.exports = function(app, db) {
  noteRoutes(app, db);
  // Тут, позже, будут и другие обработчики маршрутов 
};

Импортируем то, что получилось, в server.js:

// server.js
const express        = require('express');
const MongoClient    = require('mongodb').MongoClient;
const bodyParser     = require('body-parser');
const app            = express();
const port = 8000;
require('./app/routes')(app, {});
app.listen(port, () => {
  console.log('We are live on ' + port);
});

Обратите внимание на то, что так как базу данных мы пока не настроили, вторым аргументом передаётся пустой объект.

Теперь создаём маршрут CREATE. Синтаксис здесь довольно простой:

module.exports = function(app, db) {
  app.post('/notes', (req, res) => {
    // Здесь будем создавать заметку.
    res.send('Hello')
  });
};

Когда приложение получает POST-запрос по пути ‘/notes’, оно исполнит код внутри функции обратного вызова, передав ей объект запроса (который содержит параметры запроса или JSON-данные) и объект ответа (который, понятно, используется для ответа).

То, что у нас получилось, уже можно протестировать. Отправим, с помощью Postman, POST-запрос по адресу localhost:8000/notes.


В ответ на запрос должно прийти «Hello»

Отлично. Первый маршрут создан. Следующий шаг – добавление к запросу параметров, обработка их в API, и, наконец – сохранение заметки в базе данных.

Параметры запроса


В Postman перейдите на вкладку Body и добавьте несколько пар ключ-значение, выбрав радиокнопку x-www-form-urlencoded. А именно, первым ключом будет title, его значение – My Note Title. Второй ключ – body, его значение – What a great note.

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


Заголовок моей заметки, да и она сама – очень просты, а вы тут можете проявить фантазию

В файле note_route.js, просто выведем тело заметки в консоль.

// note_routes.js
module.exports = function(app, db) {
  app.post('/notes', (req, res) => {
    console.log(req.body)
    res.send('Hello')
  });
};

Попробуйте отправить запрос с помощью Postman, и вы увидите… undefined.

К сожалению, Express не может самостоятельно обрабатывать формы в URL-кодировке. Тут нам на помощь придёт ранее установленный пакет body-parser.

// server.js
const express        = require('express');
const MongoClient    = require('mongodb').MongoClient;
const bodyParser     = require('body-parser');
const app            = express();
const port = 8000;
app.use(bodyParser.urlencoded({ extended: true }));
require('./app/routes')(app, {});
app.listen(port, () => {
  console.log('We are live on ' + port);
});

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

{ title: 'My Note Title', body: 'What a great note.' }

Чтобы первый маршрут полностью заработал, осталось лишь настроить базу данных и добавить в неё заметку.

Для быстрого создания и настройки базы данных воспользуемся сервисом mLab. Работать с ним легко, для маленьких объёмов информации он бесплатен.

Создайте учётную запись на сайте mLab и разверните новую базу данных MongoDB. Для этого нажмите на кнопку Create New в разделе MongoDB Deployments, в появившемся окне, в разделе Plan, выберите Single-node. В списке Standard Line, выберите Sandbox и дайте базе данных имя. Далее, в окне управления базой, перейдите на вкладку Users и добавьте пользователя базы данных, задав имя и пароль.


Новый пользователь базы данных

Скопируйте с той же страницы второй URL – строку подключения к базе данных.


URL для подключения к базе данных

В корень проекта добавьте директорию config, создайте в ней файл db.js.

mkdir config 
cd config
touch db.js

В файл db.js добавьте следующее:

module.exports = {
  url : здесь будет ваш URL
};

Не забудьте добавить в URL имя пользователя и пароль (не те, что от учётной записи в mLab, а те, что создавали для базы данных). Если вы размещаете проект на Github, не забудьте включить в него файл .gitignore (вроде этого). Так вы не сделаете всеобщим достоянием имя и пароль для работы с базой.

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

// server.js
const express        = require('express');
const MongoClient    = require('mongodb').MongoClient;
const bodyParser     = require('body-parser');
const db             = require('./config/db');
const app            = express();
const port = 8000;
app.use(bodyParser.urlencoded({ extended: true }));
MongoClient.connect(db.url, (err, database) => {
  if (err) return console.log(err)
  require('./app/routes')(app, database);
  app.listen(port, () => {
    console.log('We are live on ' + port);
  });               
})

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

Добавление записей в базу данных


MongoDB хранит данные в коллекциях (collections), которые полностью оправдывают своё название. В нашем случае заметки будут храниться в коллекции, которая, как несложно догадаться, будет называться notes.

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

db.collection('notes')

Создание заметки в базе равносильно вызову команды insert для коллекции notes:

const note = { text: req.body.body, title: req.body.title}
  db.collection('notes').insert(note, (err, results) => {
}

Примечание: Для версии MongoDB выше 2.2.33 не работает. Решение найдено здесь.

После успешного завершения команды (или после того, как она, по какой-нибудь причине, не сможет выполниться), нужно либо отправить в ответ только что созданный объект заметки, либо – сообщение об ошибке. Вот код note_routes.js, дополненный с учётом этих рассуждений:

// note_routes.js
module.exports = function(app, db) {
  app.post('/notes', (req, res) => {
    const note = { text: req.body.body, title: req.body.title };
    db.collection('notes').insert(note, (err, result) => {
      if (err) { 
        res.send({ 'error': 'An error has occurred' }); 
      } else {
        res.send(result.ops[0]);
      }
    });
  });
};

Испытайте то, что получилось. Отправьте из Postman POST-запрос (с флагом x-www-form-urlencoded ), задав на вкладке Body значения полей title и body.

Ответ должен выглядеть примерно так:


Успешное добавление записи в базу

Если теперь посмотреть на базу, авторизовавшись на mLab, в ней можно будет найти только что созданную заметку.

Чтение заметок: маршрут READ


Инфраструктура, которую мы подготовили выше, подходит для всех маршрутов, поэтому теперь дело пойдёт быстрее.

Итак, мы собираемся запросить только что созданную заметку, пройдя по пути localhost:8000/notes/{id заметки}. В нашем случае путь будет выглядеть так: localhost:8000/notes/585182bd42ac5b07a9755ea3.

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

Вот как это выглядит в note_route.js:

// note_routes.js
module.exports = function(app, db) {
  app.get('/notes/:id', (req, res) => {
    
  });
  app.post('/notes', (req, res) => {
    const note = { text: req.body.body, title: req.body.title };
    db.collection('notes').insert(note, (err, result) => {
      if (err) { 
        res.send({ 'error': 'An error has occurred' }); 
      } else {
        res.send(result.ops[0]);
      }
    });
  });
};

Так же, как и раньше, мы собираемся вызвать некую команду для коллекции базы данных заметок. Применим для этого метод findOne.

// note_routes.js
module.exports = function(app, db) {
  app.get('/notes/:id', (req, res) => {
    const details = { '_id': <ТУТ БУДЕТ ID> };
    db.collection('notes').findOne(details, (err, item) => {
      if (err) {
        res.send({'error':'An error has occurred'});
      } else {
        res.send(item);
      }
    });
  });
app.post('/notes', (req, res) => {
    const note = { text: req.body.body, title: req.body.title };
    db.collection('notes').insert(note, (err, result) => {
      if (err) { 
        res.send({ 'error': 'An error has occurred' }); 
      } else {
        res.send(result.ops[0]);
      }
    });
  });
};

Идентификатор из параметров URL можно вытащить с помощью конструкции req.params.id. Однако если просто вставить строку вместо <<>> из кода выше, работать это не будет.

MongoDB требуется ID не в виде строки, а в виде специального объекта. Он называется ObjectID.

Вот что, после небольших изменений, у нас получилось:

// note_routes.js
var ObjectID = require('mongodb').ObjectID;
module.exports = function(app, db) {
  app.get('/notes/:id', (req, res) => {
    const id = req.params.id;
    const details = { '_id': new ObjectID(id) };
    db.collection('notes').findOne(details, (err, item) => {
      if (err) {
        res.send({'error':'An error has occurred'});
      } else {
        res.send(item);
      } 
    });
  });
app.post('/notes', (req, res) => {
    const note = { text: req.body.body, title: req.body.title };
    db.collection('notes').insert(note, (err, result) => {
      if (err) { 
        res.send({ 'error': 'An error has occurred' }); 
      } else {
        res.send(result.ops[0]);
      }
    });
  });
};

Испытайте это с одним из идентификаторов заметок, имеющихся в базе данных. Ответ в Postman должен выглядеть так:


Успешный запрос заметки из базы

Удаление заметок: маршрут DELETE


Удаление объектов – это практически то же самое, что их поиск в базе. Только вместо функции findOne используется функция remove. Вот полный код соответствующего пути. Здесь выделено то, что отличается от кода уже существующего метода, обрабатывающего запрос GET.

// note_routes.js
// ...
  app.delete('/notes/:id', (req, res) => {
    const id = req.params.id;
    const details = { '_id': new ObjectID(id) };
    db.collection('notes').remove(details, (err, item) => {
      if (err) {
        res.send({'error':'An error has occurred'});
      } else {
        res.send('Note ' + id + ' deleted!');
      } 
    });
  });
// ...

Обновление заметок: маршрут UPDATE


А вот и последний маршрут. Обработка запроса PUT – это, по сути, гибрид операций READ и CREATE. Сначала надо найти объект, потом – обновить его в соответствии с поступившими в запросе данными. Сейчас, если вы, испытывая предыдущий фрагмент кода, удалили свою единственную заметку, создайте ещё одну.

Вот код маршрута обновления заметок:

// note_routes.js
// ...
  app.put ('/notes/:id', (req, res) => {
    const id = req.params.id;
    const details = { '_id': new ObjectID(id) };
    const note = { text: req.body.body, title: req.body.title };
    db.collection('notes').update(details, note, (err, result) => {
      if (err) {
          res.send({'error':'An error has occurred'});
      } else {
          res.send(note);
      } 
    });
  });
// ...

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


Успешное обновление заметки

Обратите внимание на недостаток нашего примера. Если в PUT-запросе не будет тела или заголовка заметки, соответствующие поля в базе будут просто очищены.

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

Итоги


Теперь у вас есть рабочее Node API, которое поддерживает четыре основные операции CRUD. Серверная часть приложения умеет, реагируя на HTTP-запросы клиента, создавать в базе данных заметки, находить их, удалять и редактировать.

Основная цель моего рассказа – познакомить всех желающих со связкой Node + Express + MongoDB и с методикой разработки серверных приложений. Конечно, если сегодня состоялось ваше первое знакомство с этими инструментами, для того, чтобы во всё лучше вникнуть, вам понадобится почитать документацию. Однако, понимание происходящего позволит вам быстро восполнить пробелы в знаниях и приступить к работе над собственными проектами, использовав, в качестве отправной точки, приложение, которым мы с вами тут занимались.

Если у вас есть опыт работы с Node.js, Express и MongoDB в реальных проектах, может быть, вы посоветуете что-нибудь полезное новичкам? А если вы только что всё это впервые попробовали – ждём ваших впечатлений.
RUVDS.com
RUVDS – хостинг VDS/VPS серверов

Comments 30

    0
    Насколько я знаю, соединение с монгой может быть утеряно. То, как сделано сейчас (единожды коннектимся к базе при старте приложения) не очень надежно. Поэтому по-хорошему нужно перед каждым взаимодействием с базой проверять, что соединение в порядке и в случае надобности делать новый коннект. Чтоб не делать этого каждый раз, есть удобная мидлвара, которая держит объект соединения в замыкании и при каждом запросе пользователя проверяет наличие соединения, делает рекконнект (если нужно), и записывает объект базы данных в req.
      –1
      А можно просто взять mongoose и получить автоподключение + ORM.
        0

        Ну прямо скажем mongoose не решает вопрос отвалившейся БД.
        Вот примерно так можно отслеживать состояние подключения: https://github.com/simdo/rest.api/blob/master/index.js

            0
            http://mongoosejs.com/docs/connections.html:
            Note: The server option auto_reconnect is defaulted to true which can be overridden. The db option forceServerObjectId is set to false which cannot be overridden.

            Note: If auto_reconnect is on, mongoose will give up trying to reconnect after a certain number of failures. Set the server.reconnectTries and server.reconnectInterval options to increase the number of times mongoose will try to reconnect.

            // Good way to make sure mongoose never stops trying to reconnect
            mongoose.connect(uri, { server: { reconnectTries: Number.MAX_VALUE } });
            


            Т.е. в принципе нет нужны в таком функционале. Можно сделать watchdog таймер на событие отрубания и если оно не приконнектится через нужное время, значит база мертва / канал мертв.
              0

              Ну ребята, ну как же так?


              1. У меня rest микросервис.
              2. Данные обрабатываются в БД.
              3. БД удаленная.
              4. Соединение не стабильное.

              Вопрос:
              Какой ответ сервис должен отдать в случае когда клиент осуществляет api-запрос?
              Сервер в данный момент утерял соединение с БД.


              Варианты:


              • заставить клиента ожидать на линии
              • пробросить ошибку получения данных из БД ввиду отсутствия соединения?
              • ответить что в данный момент БД недоступна
              • ваши предложения?
                0
                Если соединение нестабильное, то у меня плохие новости — клиентам лучше поискать другие сервисы. Доступ к СУБД — одна из самых критичных к общей производительности системы вещей. Можно сделать локальное кеширование, сделать несколько копий базы с репликацией (но все-равно мастер на запись будет один). А вообще, да, 2 варианта:
                1. Показывать пользователю крутилку, что мы что-то делаем с разумным временем ожидания
                2. Отдать управление с показом варнинга, что операция продолжается в фоне и нужно подождать ее завершения (если настроены реплики с доступом на чтение — клиенту не обязательно ждать завершения операции сохранения для дальнейших операций поиска).
                Опять же — все зависит от задачи, допускается такое поведение или нет.
                  0

                  Мой посыл заключается в том что приложение будет валить ошибки изза отсутствия связи с БД. Эти ошибки вываливать на фронт клиенту некошерно. Вы мне рассказываете что mongoose там что-то сам делает и хорошо.

        0
        так там автоматически создаётся пул из 5 соединений. Его размер хранится в db.poolSize
          0
          В db.url можно добавить параметр autoReconnect=true, хотя оно вроде бы было включено по-умолчанию
          –1
          Еще дополнение. Где-то в документации натыкался, что лучше использовать express.Router()

          Кстати, мне кажется что его удобнее использовать и не надо пробрасывать app внутрь.
            0
            а даже если и пробрасывать app — зачем пробрасывать db, когда ее можно сделать свойством app и обращаться через штатные set / get методы.
              0
              Это мне вопрос? Я не автор статьи.
                0
                Вопрос? Нет, это просто дополнение к комментарию — к мысли о том, что можно не прокидывать лишнее в виде параметров.
                  0
                  Ок, показалось что вопрос адресовался мне.

                  Интересно, что лучше: проброс app внутрь модуля или все же делать как middleware?

                  Сам делаю middleware.
                    0
                    app передается в каждом запросе как req.app. Т.е. инстанс клиента бд можно в момент инициализации сохранить в app через app.set(), а потом доставать в роутинге через req.app.get().
                      0
                      Использую mongooseJS в нем такая необходимость отсутствует.

                      Но если что-то нужно в middleware то да, правильный подход.
              0
              http://expressjs.com/ru/guide/routing.html
              Последние абзацы
                0
                К сожалению описание, где это видел не помню. Но это не в официальной доке. Возможно где-то на GitHub в обсуждениях. Увы.

                Но для себя выбрал путь, когда роутинг это отдельный файл с описанием всех АПИ, а middleware разложены по модулям и импортируются в роут. Это позволяет наглядно видеть все АПИ, и при этом быстро получить доступ к реализации. Так же это позволяет вынести определение АПИ из index.js который теперь состоит из импортов модулей и их подключений. Файл становится небольшим и легко читаемым.
              0
              Желательно что бы URL имел примерно такой вид:
              /api/api_version/model_name
              Может оказаться, что API — это часть Вашего большого проекта.
              У Вашего проекта может быть несколько версий API.
              Хорошая статья про API
                0
                /api/api_version/model_name

                Не обязательно.
                Есть несколько стратегий версионирования, и приведенная вами только одна из них.

                Альтернативы:
                1. Версия записывается в headers
                2. Версионируем каждый из ресурсов, т.е. /api/model_name/api_version
                3. ContentType, Accept
                и т.д.
                А, ну и вы вообще можете версионировать для каждого пользователя (так делают в Stripe, не совсем для каждого, но после регистрации для пользователя фиксируется версия api, т.е. пользователь совсем не знает какую версию он использует, и доку видит ту, которая для его api)
                  0
                  Stripe просто монстры API. Не каждый такое осилит реализовать.
                    0
                    Да это было просто пример. Я бы такое врятли стал в своих проектах делать, масштаб не тот.
                      0
                      Заинтересовался, что ж такого они наворотили, делюсь для будущих читателей комментариев.
                      stripe.com/blog/api-versioning

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

                      Это часто рассматривается как основная схема управления версиями с именами, такими как v1, v2 и v3, которые передаются в качестве префикса для URL-адреса (например, /v1/widgets ) или через HTTP-заголовок, например Accept. Это может работать, но имеет существенный недостаток в изменениях между версиями, которые настолько велики и настолько эффективны для пользователей, что это почти так же больно, как повторная интеграция с нуля. Это также не явная победа, потому что будет класс пользователей, которые не хотят или не могут обновиться и попасть в старые версии API. Поставщикам необходимо сделать сложный выбор между версиями API для выхода на пенсию и расширением, сокращающим этих пользователей, или сохранением старых версий навсегда при значительных затратах. Хотя поставщики, поддерживающие старые версии, могут показаться на первый взгляд полезными для пользователей, они также платят косвенно в виде снижения прогресса в улучшении. Вместо того, чтобы работать с новыми функциями, время разработки перенаправляется на сохранение старого кода.

                      В Stripe мы реализуем управление версиями со скользящими версиями, имена которых указаны с датой их выпуска (например, 2017-05-24 ). Несмотря на то, что они несовместимы в обратном направлении, каждый из них содержит небольшой набор изменений, которые делают инкрементные обновления относительно легкими, так что интеграция может оставаться актуальной.

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

                      Некоторые читатели, возможно, уже заметили, что Stripe API также определяет основные версии с использованием префиксного пути (например, /v1/charges ). Хотя мы оставляем за собой право использовать это в какой-то момент, это вряд ли изменится в течение некоторого времени. Как отмечалось выше, основные изменения в версии, как правило, делают обновление болезненным, и нам сложно представить редизайн API, который достаточно важен, чтобы оправдать этот уровень воздействия пользователя. Наш нынешний подход был достаточным для почти сто несовместимых обновлений за последние шесть лет.

                      API versioning schemes
                      A common approach to allow forward progress in web APIs is to use versioning. Users specify a version when they make requests and API providers can make the changes they want for their next version while maintaining compatibility in the current one. As new versions are released, users can upgrade when it’s convenient for them.

                      This is often seen as a major versioning scheme with names like v1, v2, and v3 that are passed as a prefix to a URL (like /v1/widgets) or through an HTTP header like Accept. This can work, but has the major downside of changes between versions being so big and so impactful for users that it’s almost as painful as re-integrating from scratch. It’s also not a clear win because there will be a class of users that are unwilling or unable to upgrade and get trapped on old API versions. Providers then have to make the difficult choice between retiring API versions and by extension cutting those users off, or maintaining the old versions forever at considerable cost. While having providers maintain old versions might seem at first glance to be beneficial to users, they’re also paying indirectly in the form of reduced progress on improvements. Instead of working on new features, engineering time is diverted to maintaining old code.

                      At Stripe, we implement versioning with rolling versions that are named with the date they’re released (for example, 2017-05-24). Although backwards-incompatible, each one contains a small set of changes that make incremental upgrades relatively easy so that integrations can stay current.

                      The first time a user makes an API request, their account is automatically pinned to the most recent version available, and from then on, every API call they make is assigned that version implicitly. This approach guarantees that users don’t accidentally receive a breaking change and makes initial integration less painful by reducing the amount of necessary configuration. Users can override the version of any single request by manually setting the Stripe-Version header, or upgrade their account’s pinned version from Stripe’s dashboard.

                      Some readers might have already noticed that the Stripe API also defines major versions using a prefixed path (like /v1/charges). Although we reserve the right to make use of this at some point, it’s not likely to change for some time. As noted above, major version changes tend to make upgrades painful, and it’s hard for us to imagine an API redesign that’s important enough to justify this level of user impact. Our current approach has been sufficient for almost a hundred backwards-incompatible upgrades over the past six years.
                  +1
                  *не увидел, что это перевод

                  Вопросы, которые я хотел бы задать автору:

                  А что делает ваше CRUD API именно REST API? Какая часть статьи показывает наличие REST функционала в вашем CRUD API?

                  вам надо понимать, что такое REST API, иметь представление об операциях CRUD
                  Не могли бы вы добавить в вашу статью хотя бы небольшое пояснение по поводу того, что такое CRUD и что такое REST, и как сделать REST API на основе CRUD операций, и чем это лучше например REST поверх RPC, и почему вы выбрали именно CRUD? И как сможете всегда-всегда обходиться четырьмя операциями в реальной жизни, т.к. если ввести хоть что-то, отличное от CRUD, то это уже будет не CRUD API, а просто некое API?

                  Освоив эту схему, вы сможете понять, как, с помощью Node, организовать практически любой необходимый REST-маршрут.
                  Не любое API с урлами является RESTful. Я честно говоря не вижу в вашем API ничего от REST.

                  пойду переведу и задам.
                    0
                    Задал вопрос автору. Ответ принесу (если будет).
                      +1
                      https://medium.com/@scottdomes/hi-igor-e5192ded55a1#.xsii8g1y7
                      Автор толком ничего не ответил. Я так понял, что автор считает, что если CRUD поверх HTTP, то этого достаточно, и это уже REST и всё тут. Гипермедийность с его стороны в беседе не упомянута. Хотя на мой взгляд это основополагающий принцип, отличающий «просто апи» от «рест апи» примерно так же как «просто текст» отличается от «текста с ссылками». Мне кажется отличие это разительное, и ситуация, когда вам нужно знать всю карту всех урлов заранее и она никак не вытекает из ответов сервера кардинально отличается от гипермедийного рест, где задумано, что, как и в HTML, вы получаете ссылки в ответах на запросы, и можете путешествовать дальше.

                      Подытожу: просто вываливать наружу методы хранилища проксируя через ноду недостаточно, чтобы называться REST.
                    0
                    как я понял из документации более лучшей практикой является использование express.Router() вместо передачи app как параметра.

                    в файле users.js
                    const express = require('express');
                    const router = express.Router();
                    
                    router.get('/', function(req, res, next) {
                      res.json({ a: 1 });
                    });
                    

                    и в файле app.js
                    const express = require('express');
                    const app = express();
                    
                    app.use('/users', require('./users'));
                    
                      0
                      Для mongoDB версии 3.x код немного другой:
                      const express        = require('express');
                      const MongoClient    = require('mongodb').MongoClient;
                      const bodyParser     = require('body-parser');
                      const db             = require('./config/db');
                      const app            = express();
                      const port = 8000;
                      app.use(bodyParser.urlencoded({ extended: true }));
                      MongoClient.connect(db.url, (err, database) => {
                        if (err) return console.log(err)
                                            
                        // Make sure you add the database name and not the collection name
                        const db = database.db("note-api")
                        require('./app/routes')(app, db);
                        app.listen(port, () => {
                          console.log('We are live on ' + port);
                        });               
                      })
                        0
                        Спасибо!

                      Only users with full accounts can post comments. Log in, please.