Создание серверной части REST API с использованием Express.js
Это Medium.com версия исходной статьи, которую я написал в LinkedIn. Вы можете увидеть это здесь: Версия для LinkedIn
Здравствуйте, меня зовут Антонио Эрделяк, я 16-летний разработчик JavaScript. Это моя первая статья и руководство.
В этой статье я буду создавать серверную часть REST API, используя Express.js узла. Я хочу подчеркнуть, что я купил курс Thinkser, чтобы узнать, как это сделать, и я буду использовать это руководство, чтобы предоставить вам бесплатную информацию и повысить свои навыки.
Поскольку это мой первый учебник, я настоятельно рекомендую хорошие знания JavaScript, поскольку сомневаюсь, что смогу хорошо объяснить, что делаю. Ожидается, что он также знает, как работать в Postman (отправлять запросы POST, PUT, DELETE и устанавливать заголовки). Также было бы неплохо иметь некоторый опыт работы с MongoDB, но это не совсем необходимо.
Загрузка нашего проекта
Мы начнем с начальной загрузки нашего проекта с помощью express-generate с небольшими изменениями. Вы можете скачать стартовый проект отсюда: Стартовый проект
После того, как вы распаковали проект, откройте его и выполните команду npm-install в указанном каталоге (где находятся app.js, config, models и т. Д.)
Давайте начнем.
1. Создание Nodemon
Начнем с применения Nodemon к нашему проекту, чтобы наш сервер автоматически обновлялся каждый раз, когда мы меняем строку кода.
В package.json внесите следующие изменения:
"scripts": { "start": "node ./app.js", //Changes "dev": "nodemon ./app.js", //Changes over "test": "echo \"Error: no test specified\" && exit 1" },
Это позволит нам запустить команду npm run dev в терминале, и Nodemon запустит наш сервер. Запустите npm run dev в терминале, чтобы запустить сервер и перейти к следующему шагу.
2. Создание модели пользователя.
Мы собираемся использовать библиотеку Mongoose, чтобы легко создавать схемы и модели для пользователей, статей, комментариев и т. Д.
В папке Модели создайте новый файл с именем User.js.
Давайте добавим несколько первых строк кода в User.js:
var mongoose = require('mongoose'); var uniqueValidator = require('mongoose-unique-validator'); var crypto = require('crypto'); var jwt = require('jsonwebtoken'); var secret = require('../config').secret; var UserSchema = new mongoose.Schema({ username: {type: String, unique: true, required: [true, "cannot be empty."], lowercase: true, index: true}, email: {type: String, unique: true, required: [true, "cannot be empty."], lowercase: true, index: true}, bio: String, image: String, salt: String, hash: String }, {timestamps: true});
Отлично, мы только что создали нашу первую схему с использованием Mongoose. Мы определили имя пользователя как обязательное, уникальное и строчную строку. Мы сделали то же самое и с электронной почтой. Для других свойств мы просто установили их как Strings.
Давайте применим библиотеку uniqueValidator к нашему файлу User.js:
UserSchema.plugin(uniqueValidator, {message: "is already taken."});
Давайте добавим несколько методов в User.js
... var UserSchema = new mongoose.Schema({ username: {type: String, unique: true, required: [true, "cannot be empty."], lowercase: true, index: true}, email: {type: String, unique: true, required: [true, "cannot be empty."], lowercase: true, index: true}, bio: String, image: String, salt: String, hash: String }, {timestamps: true}); UserSchema.plugin(uniqueValidator, {message: "is already taken."}); //^ OLD CODE, just so know where we left off. ^ //NEW CODE BENEATH THIS COMMENT UserSchema.methods.setPassword = function(password){ this.salt = crypto.randomBytes(16).toString('hex'); this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex'); }; UserSchema.methods.validPassword = function(password){ var hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex'); return this.hash === hash; }; UserSchema.methods.generateJWT = function(){ var today = new Date(); var exp = new Date(today); exp.setDate(today.getDate()+60); return jwt.sign({ id: this._id, username: this.username, exp: parseInt(exp.getTime()/1000) }, secret) }; UserSchema.methods.toAuthJSON = function(){ return { username: this.username, email: this.email, bio: this.bio, image: this.image, token: this.generateJWT() }; };
Отлично, теперь мы определили следующие методы:
- setPassword - используется для генерации пароля путем случайного создания пользовательских свойств hash и salt для шифрования пароля, предоставленного пользователем с помощью Crypto библиотека
- validPassword - используется для сравнения предоставленного пароля с фактическим паролем пользователя.
- generateJWT - используется для создания веб-токена JSON, срок действия которого истекает через 60 дней с момента создания, и который будет храниться и использоваться в Frontend’s window.localStorage ()
- toAuthJSON - используется для возврата указанных свойств пользователя (имя пользователя, адрес электронной почты, биография…).
Наконец, давайте добавим UserSchema к моделям mongoose.
mongoose.model('User', UserSchema);
Теперь давайте потребуем модель User в app.js (найдите строку «app.use (require ('./ routes'));» и внесите следующие изменения) :
... //NEW LINE OF CODE BENEATH THIS COMMENT require('./models/User'); //OLD CODE BENEATH THIS COMMENT app.use(require('./routes')); /// catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); ...
Отлично, теперь сервер должен зарегистрировать модель пользователя, и вы должны увидеть сообщение в терминале, которое будет выглядеть следующим образом:
Listening on port 8000 Mongoose: users.ensureIndex({ username: 1 }) { unique: true, background: true } Mongoose: users.ensureIndex({ email: 1 }) { unique: true, background: true }
Если у вас есть точное сообщение в терминале, поздравляю, вы закончили свою первую модель в этом уроке!
3. Создание паспорта.
Теперь мы собираемся создать паспорт, который мы будем использовать при аутентификации пользователя.
В папке config создайте новый файл с именем Passport.js со следующим кодом:
var passport = require('passport'); var LocalStrategy = require('passport-local'); var mongoose = require('mongoose'); var User = mongoose.model('User'); passport.use(new LocalStrategy({ usernameField: 'user[email]', passwordField: 'user[password]' }, function(email, password, done){ User.findOne({email: email}).then(function(user){ if(!user || !user.validPassword(password)){ return done(null, false, {errors: {"email or password":"is invalid."}}) } return done(null, user); }).catch(done); }));
Мы будем использовать это при аутентификации данных для входа нашего пользователя, если адрес электронной почты и пароль верны, модель пользователя будет возвращена, в противном случае будет возвращена ошибка с надписью «электронная почта или пароль недействителен ».
Зарегистрируйте Passport.js в app.js:
... require('./models/User'); //OLD Code on top, just so you can know where we left off. //new code beneath this comment require('./config/passport'); //old code beneath this comment app.use(require('./routes'));
Отлично, вы создали паспорт и успешно зарегистрировали его в приложении. Проверьте терминал на наличие ошибок (их не должно быть) и переходите к следующему шагу.
4. Создание маршрута авторизации.
Мы собираемся создать файл, отвечающий за то, должен ли пользователь быть авторизован или не авторизован.
В папке routes / создайте новый файл с именем auth.js со следующим кодом:
var jwt = require('express-jwt'); var secret = require('../config').secret; function getTokenFromHeaders(req){ if(req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Token'){ return req.headers.authorization.split(' ')[1]; } return null; } var auth = { required: jwt({ secret: secret, userProperty: 'payload', getToken: getTokenFromHeaders }), optional: jwt({ secret: secret, userProperty: 'payload', credentialsRequired: false, getToken: getTokenFromHeaders }) }; module.exports = auth;
Давайте объясним, что это делает.
Функция getTokenFromHeaders проверяет наличие токена в заголовке отправленного запроса и возвращает его, если он есть, в противном случае возвращает значение null.
Объект аутентификации будет вызываться в наших маршрутах в зависимости от того, хотим ли мы, чтобы пользователю требовалось войти в систему, чтобы увидеть конкретный маршрут или выполнить с ним действие, или если нам все равно (необязательно), если он авторизован или нет.
5. Создание пользовательского маршрута.
Теперь мы собираемся создать маршрут, который будет обрабатывать регистрацию пользователя, вход в систему, обновление пользователя и т. Д.
В папке routes / api / создайте новый файл с именем users.js.
Начнем с создания возможности регистрации пользователя. Внесите следующие изменения в users.js:
var mongoose = require('mongoose'); var router = require('express').Router(); var auth = require('../auth'); var User = mongoose.model('User'); var passport = require('passport'); router.post('/users', function(req,res,next){ var user = new User(); user.username = req.body.user.username; user.email = req.body.user.email; user.setPassword(req.body.user.password); user.save().then(function(){ return res.json({user: user.toAuthJSON()}); }).catch(next); });
Как видите, мы используем модель Пользователь и в настройках это свойства имя пользователя, адрес электронной почты и пароль, после чего мы сохраняем его и возвращаем. используя созданную нами функцию user.toAuthJSON ().
Теперь давайте добавим возможность входа в систему. Добавьте следующие изменения в users.js:
... router.post('/users/login', function(req,res,next){ if(!req.body.user.email){ return res.status(422).json({errors: {email: "can't be blank."}}); } if(!req.body.user.password){ return res.status(422).json({errors: {password: "can't be blank."}}); } passport.authenticate('local', {session: false}, function(err, user, info){ if(err){return next(err);} if(user){ user.token = user.generateJWT(); return res.json({user: user.toAuthJSON()}); } else { return res.status(422).json(info); } })(req,res,next) });
Мы начинаем с проверки, предоставил ли пользователь адрес электронной почты и пароль, в противном случае мы немедленно возвращаем ошибку. Если все в порядке, мы используем паспорт для аутентификации пользователя, возвращаем ошибки, если таковые имеются, и, что более важно, возвращаем вошедшего в систему пользователя.
Давайте добавим возможность пользователю проверять свой профиль / модель пользователя. Добавьте следующие изменения:
router.get('/user', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(!user){return res.sendStatus(401);} return res.json({user: user.toAuthJSON()}); }).catch(next); });
Теперь пользователь может видеть свою информацию, но только если он вошел в систему (требуется авторизация).
Наконец, давайте добавим пользователю возможность обновлять свою информацию. Добавьте в код следующие изменения:
router.put('/user', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(!user){return res.sendStatus(401);} if(typeof req.body.user.username !== 'undefined'){ user.username = req.body.user.username; } if(typeof req.body.user.email !== 'undefined'){ user.email = req.body.user.email; } if(typeof req.body.user.bio !== 'undefined'){ user.bio = req.body.user.bio; } if(typeof req.body.user.image !== 'undefined'){ user.image = req.body.user.image; } if(typeof req.body.user.password !== 'undefined'){ user.setPassword(req.body.user.password); } return user.save().then(function(){ return res.json({user: user.toAuthJSON()}); }); }).catch(next); });
Теперь пользователь может обновить свою информацию, если он вошел в систему. Мы также проверяем, являются ли новые обновления пользователя действительными, и оставляем без изменений те, которых нет.
Давайте создадим промежуточное ПО, которое будет показывать нам ошибки (уже отправленное письмо, неверная информация и т. Д.). Добавьте следующие изменения в users.js:
router.use(function(err,req,res,next){ if(err.name === 'ValidationError'){ return res.json({ errors: Object.keys(err.errors).reduce(function(errors ,key){ errors[key] = err.errors[key].message; return errors; }, {}) }) } return next(err); });
Не пугайтесь этого, все, что он делает, это сортирует объект грязных ошибок как simple {errors: {email: «не может быть пустым.»}} тип объекта.
Завершите файл users.js, добавив последнюю строку кода:
module.exports = router;
Наконец, перейдите к index.js в той же папке, что и users.js, и выполните следующие действия:
var router = require('express').Router(); // existing code on top //new code router.use('/', require('./users')); //existing code beneath this comment module.exports = router;
1. Отлично, теперь это пример того, что мы отправляем для регистрации пользователя:
С помощью Postman отправьте запрос POST со следующим телом на маршрут localhost: 8000 / api / users:
{ "user":{ "username":"Test", "email":"[email protected]", "password":"test" } }
2. Это пример того, что мы отправляем для входа пользователя:
С помощью Postman отправьте запрос POST со следующим телом на маршрут localhost: 8000 / api / users / login:
{ "user":{ "email":"[email protected]", "password":"test" } }
3. Это пример того, что мы отправляем для авторизации пользователя:
Примечание. Обязательно скопируйте токен, который вы получаете при входе или регистрации, и перейдите в настройки Заголовки Postman. Добавьте Авторизация и установите для него значение «Токен-пример» (без кавычек). Он должен выглядеть так:
Используя Postman, отправьте запрос GET на маршрут localhost: 8000 / api / user.
4. Это пример того, что мы отправляем для обновления информации вошедшего в систему пользователя:
Примечание. Обязательно скопируйте токен, который вы получаете при входе или регистрации, и перейдите в Заголовки. Добавьте Авторизацию и установите для него значение «Токен-пример».
Используя Postman, отправьте запрос PUT со следующим телом на маршрут localhost: 8000 / api / user:
{ "user":{ "email":"[email protected]", "username":"newusername" } }
теперь вы должны получить нового, обновленного пользователя.
6. Создание профилей
Вернемся к models / User.js и добавим новый метод:
UserSchema.methods.toProfileJSONFor = function(user){ return { username: this.username, bio: this.bio, image: this.image, following: false // we will change this later }; };
Мы будем вызывать это, чтобы получить только ту информацию, которую пользователь хотел бы, чтобы другой пользователь видел, без его токена и электронной почты.
Теперь перейдем к routes / api / и создадим новый файл с именем profiles.js.
Давайте создадим промежуточное ПО, которое будет проверять, существует ли пользователь в любое время, когда вызывается / profiles / someUser. Добавьте в profiles.js следующее:
var mongoose = require('mongoose'); var User = mongoose.model('User'); var router = require('express').Router(); var auth = require('../auth'); router.param('username', function(req,res,next,username){ User.findOne({username: username}).then(function(user){ if(!user){return res.sendStatus(404);} req.profile = user; return next(); }).catch(next); });
Чтобы объяснить, что это делает, каждый раз, когда мы запрашиваем маршрут /: username, это промежуточное ПО проверяет, существует ли это : username , а если этого не произойдет, возникнет ошибка 404 .
Давайте добавим маршрут, который вернет указанный профиль, если он существует:
router.get('/:username', auth.optional, function(req,res,next){ if(req.payload){ User.findById(req.payload.id).then(function(user){ if(!user){return res.json({profile: req.profile.toProfileJSONFor(false)})} return res.json({profile: req.profile.toProfileJSONFor(user)}); }).catch(next); } else { return res.json({profile: req.profile.toProfileJSONFor(false)}); } });
Мы проверяем, пользователь, посещающий профиль, вошел в систему или нет, и передаем зарегистрированного пользователя в toProfileJSONFor (пользователь) или не проходит, если он не вошел в систему toProfileJSONFor (false).
Не забудьте добавить последнюю строчку:
module.exports = router;
Теперь перейдите в index.js и сделайте то же самое, что и для users.js:
router.use('/profiles', require('./profiles'));
Теперь перейдите в Почтальон и отправьте запрос GET на localhost: 8000 / api / profiles / test (или localhost: 8000 / api / profiles / любое_имя_в_ базе данных)
7. Создание модели статьи.
В папке models / создайте новый файл с именем Article.js.
Начнем с создания для него схемы. Так же, как и для модели Пользователь.
var mongoose = require('mongoose'); var uniqueValidator = require('mongoose-unique-validator'); var slug = require('slug'); var User = mongoose.model('User'); var ArticleSchema = new mongoose.Schema({ slug: {type: String, lowercase: true, unique: true}, title: String, description: String, body: String, tagList:[{type: String}], favoritesCount: {type: Number, default: 0}, author: {type: mongoose.Schema.Types.ObjectId, ref:'User'} }, {timestamps: true});
Давайте добавим к нему плагин uniqueValidator
ArticleSchema.plugin(uniqueValidator, {message: "is already taken."});
Давайте реализуем метод slugify, который создаст уникальный слаг (пример: new-article-title- 7aXm9cms).
ArticleSchema.methods.slugify = function(){ this.slug = slug(this.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36); };
Давайте удостоверимся, что пуля всегда установлена.
ArticleSchema.pre('validate', function(next){ if(!this.slug){ this.slugify(); } return next(); });
И последний метод, toJSONFor (пользователь), который вернет заголовок, заголовок, текст статьи и т. Д.
ArticleSchema.methods.toJSONFor = function(user){ return { slug: this.slug, title: this.title, description: this.description, body: this.body, tagList: this.tagList, favoritesCount: this.favoritesCount, favorited: user ? user.isFavorite(this._id) : false, createdAt: this.createdAt, updatedAt: this.updatedAt, author: this.author.toProfileJSONFor(user) }; };
Не забудьте создать модель в последней строке в Article.js
mongoose.model('Article', ArticleSchema);
Наконец, перейдите в app.js и добавьте к нему модель Article.
require('./models/User'); /*New code*/ require('./models/Article'); require('./config/passport'); app.use(require('./routes'));
Большой! проверьте терминал, если есть какие-либо ошибки, убедитесь, что вы не допустили опечаток, и пытайтесь исправить их, пока не получите такое сообщение:
[nodemon] 1.11.0 [nodemon] to restart at any time, enter `rs` [nodemon] watching: *.* [nodemon] starting `node ./app.js` Listening on port 8000 Mongoose: users.ensureIndex({ username: 1 }) { unique: true, background: true } Mongoose: users.ensureIndex({ email: 1 }) { unique: true, background: true }
8. Маршруты статей
В routes / api / создайте новый файл с именем article.js.
Начнем с создания маршрута для создания статьи, это будет работать только если пользователь вошел в систему, потому что нам нужно , чтобы указать автора для статьи (article.author = user).
var mongoose = require('mongoose'); var router = require('express').Router(); var Article = mongoose.model('Article'); var User = mongoose.model('User'); var auth = require('../auth'); router.post('/', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(!user){return res.sendStatus(401);} var article = new Article(req.body.article); article.author = user; return article.save().then(function(){ return res.json({article: article.toJSONFor(user)}) }); }).catch(next); });
Давайте создадим промежуточное ПО, которое будет возвращать 404 ошибку, если : article не существует каждый раз, когда мы ищем : article и установите для него значение req.article it, если оно существует.
router.param('article', function(req,res,next,slug){ Article.findOne({slug: slug}) .populate('author') .then(function(article){ if(!article){ return res.sendStatus(404); } req.article = article; return next(); }).catch(next); });
Создание маршрута для получения указанной статьи с помощью slug
router.get('/:article', auth.optional, function(req,res,next){ Promise.all([ req.payload ? User.findById(req.payload.id) : null, req.article.populate('author').execPopulate() ]).then(function(results){ var user = results[0]; return res.json({article: req.article.toJSONFor(user)}); }).catch(next); });
Здесь мы используем действие Promise для Async, которое проверяет, есть ли вошедший в систему пользователь, и заполняет поле autor в req.article (статья найдена с помощью : article) , и возвращает статью, отображаемую для пользователя, или нет (если есть не авторизованный пользователь).
Теперь давайте создадим маршрут, который позволит обновить статью:
router.put('/:article', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(req.article.author._id.toString() === req.payload.id.toString()){ if(typeof req.body.article.title !== 'undefined'){ req.article.title = req.body.article.title; } if(typeof req.body.article.description !== 'undefined'){ req.article.description = req.body.article.description; } if(typeof req.body.article.body !== 'undefined'){ req.article.body = req.body.article.body; } return req.article.save().then(function(){ return res.json({article: req.article.toJSONFor(user)}); }); } else { return res.sendStatus(403); } }).catch(next); });
Здесь требуется вошедший пользователь, потому что нам нужно разрешить обновление только для автора статьи. Если пользователь не вошел в систему или пользователь не, автор, выдается ошибка 403 .
Точно так же мы собираемся создать маршрут, который позволит удалить статью:
router.delete('/:article', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(req.article.author._id.toString() === req.payload.id.toString()){ req.article.remove().then(function(){ return res.sendStatus(204); }); } else { return res.sendStatus(403); } }).catch(next); });
Здесь также требуется вошедший в систему пользователь по тем же причинам.
Не забудьте написать последнюю строку:
module.exports = router;
Наконец, перейдите в index.js в той же папке и добавьте article.js:
router.use('/', require('./users')); router.use('/profiles', require('./profiles')); /*new code*/ router.use('/articles', require('./articles'));
Выполните тестирование маршрута с помощью Postman.
9. Комментарии
Начнем с создания модели Комментарий. В models / добавьте новый файл Comment.js
var mongoose = require('mongoose'); var CommentSchema = new mongoose.Schema({ body: String, author: {type: mongoose.Schema.Types.ObjectId, ref:'User'}, article: {type: mongoose.Schema.Types.ObjectId, ref:'Article'} }, {timestamps: true}); CommentSchema.methods.toJSONFor = function(user){ return { id: this._id, body: this.body, author: this.author.toProfileJSONFor(user) }; }; mongoose.model('Comment', CommentSchema);
Очень простая схема для простой модели.
Не забудьте добавить Comment.js в app.js.
require('./models/User'); require('./models/Article'); /*new code*/require('./models/Comment'); require('./config/passport'); app.use(require('./routes'));
Ничего не меняйте, кроме / * нового кода * / строки.
Теперь вернемся к Article.js (Модель) и внесем некоторые изменения.
var ArticleSchema = new mongoose.Schema({ slug: {type: String, lowercase: true, unique: true}, title: String, description: String, body: String, tagList:[{type: String}], favoritesCount: {type: Number, default: 0}, author: {type: mongoose.Schema.Types.ObjectId, ref:'User'}, /*new code*/comments: [{type: mongoose.Schema.Types.ObjectId, ref:'Comment'}] }, {timestamps: true});
Оставьте все без изменений кроме / * нового кода * / строки. Это добавляет модели комментариев в виде массива внутри статьи.
Вернемся к article.js в routes / api / article.js и добавим немного кода.
Начните с импорта модели комментария вверху.
var Comment = mongoose.model('Comment');
Теперь давайте создадим новый маршрут, который позволит нам создать комментарий к существующей статье, если мы вошли в систему.
router.post('/:article/comments', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(!user){return res.sendStatus(401);} var comment = new Comment(req.body.comment); comment.author = user; comment.article = req.article; comment.save(); req.article.comments.push(comment); return req.article.save().then(function(){ return res.json({comment: comment.toJSONFor(user)}); }); }).catch(next); });
Здесь должны существовать и req.article, и зарегистрированный пользователь, потому что нам нужно указать автора комментария и статья.
Давайте создадим маршрут, по которому будут отображаться все комментарии в статье:
router.get('/:article/comments', auth.optional, function(req,res,next){ Promise.resolve(req.payload ? User.findById(req.payload.id) : null).then(function(user){ return req.article.populate({ path: 'comments', populate: { path: 'author' }, options: { sort: { createdAt: 'desc' } } }).execPopulate().then(function(){ return res.json({comments: req.article.comments.map(function(comment){ return comment.toJSONFor(user); })}); }); }).catch(next); });
Теперь вы уже знакомы с Promise, поэтому должно быть ясно, что это асинхронное действие, которое заполняет автора и комментария. сортирует комментарии по дате.
Давайте создадим промежуточное ПО, которое будет выдавать ошибку 404 , если : comment не существует, и установим его на req.comment, если он существует.
router.param('comment', function(req,res,next,id){ Comment.findById(id).then(function(comment){ if(!comment){return res.sendStatus(404);} req.comment = comment; return next(); }).catch(next); });
Наконец, давайте создадим маршрут, который позволит разрешить удаление комментария для автора комментария:
router.delete('/:article/comments/:comment', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(req.comment.author._id.toString() === req.payload.id.toString()){ req.article.comments.remove(req.comment._id); return req.article.save() .then(Comment.findOne({_id: req.comment._id}).remove().exec()) .then(function(){ return res.sendStatus(204); }); } else { return res.sendStatus(403); } }).catch(next); });
Проверьте маршруты комментария с помощью комментариев Postman, GET и DELETE с идентификатором.
10. Добавление статьи в избранное
Вернемся к models / User.js и внесем некоторые изменения.
var UserSchema = new mongoose.Schema({ username: {type: String, lowercase: true, required: [true, "can't be blank."], unique: true, index: true}, email: {type: String, lowercase: true, required: [true, "can't be blank."], unique: true, index: true}, bio: String, image: String, salt: String, hash: String, /*new code*/favorites: [{type: mongoose.Schema.Types.ObjectId, ref:'Article'}], }, {timestamps: true});
Ничего не редактировать, кроме / * нового кода * / строки.
Давайте добавим в модель User несколько методов для взаимодействия с недавно созданным массивом Favorites.
UserSchema.methods.favorite = function(id){ if(this.favorites.indexOf(id) === -1){ this.favorites.push(id); } return this.save(); }; UserSchema.methods.unfavorite = function(id){ this.favorites.remove(id); return this.save(); }; UserSchema.methods.isFavorite = function(id){ return this.favorites.some(function(favoriteId){ return id.toString() === favoriteId.toString(); }); };
Каждая строка говорит сама за себя, первые 2 метода (избранное и исключить) должны заканчиваться на return this.save ();
Вернемся к models / Article.js и добавим метод:
Не забудьте импортировать Модель пользователя вверху:
var User = mongoose.model('User');
Теперь вы можете использовать его для создания метода:
ArticleSchema.methods.updateFavoriteCount = function(){ var article = this; return User.count({favorites: {$in: [article._id]}}).then(function(count){ article.favoritesCount = count; return article.save(); }); };
Этот метод возвращает количество людей (пользователей), у которых есть определенная статья в массиве избранного .
Измените метод toJSONFor со следующими обновлениями:
ArticleSchema.methods.toJSONFor = function(user){ return { slug: this.slug, title: this.title, description: this.description, body: this.body, tagList: this.tagList, favoritesCount: this.favoritesCount, /*new code*/ favorited: user ? user.isFavorite(this._id) : false, createdAt: this.createdAt, updatedAt: this.updatedAt, author: this.author.toProfileJSONFor(user) }; };
Теперь мы можем вернуться к /routes/api/articles.js и добавить новые маршруты:
router.post('/:article/favorite', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(!user){return res.sendStatus(401);} return user.favorite(req.article._id).then(function(){ return req.article.updateFavoriteCount().then(function(){ return res.json({article: req.article.toJSONFor(user)}); }); }); }).catch(next); }); router.delete('/:article/favorite', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(!user){return res.sendStatus(401);} return user.unfavorite(req.article._id).then(function(){ return req.article.updateFavoriteCount().then(function(){ return res.json({article: req.article.toJSONFor(user)}); }) }); }).catch(next); });
Оба маршрута имеют одинаковые путь и параметр, но разный тип запроса (POST & DELETE). Эти маршруты вызывают функции, которые мы определили несколько минут назад в User.js и Article.js.
Проверьте функции добавления в избранное / удаления в избранное в Почтальоне.
11. Следующие пользователи
Вернемся к /model/User.js и внесем некоторые изменения в схему:
var UserSchema = new mongoose.Schema({ username: {type: String, lowercase: true, required: [true, "can't be blank."], unique: true, index: true}, email: {type: String, lowercase: true, required: [true, "can't be blank."], unique: true, index: true}, bio: String, image: String, salt: String, hash: String, favorites: [{type: mongoose.Schema.Types.ObjectId, ref:'Article'}], /*new code*/following: [{type: mongoose.Schema.Types.ObjectId, ref:'User'}] }, {timestamps: true});
Теперь давайте добавим несколько новых методов в User.js, которые будут взаимодействовать с следующим массивом.
UserSchema.methods.follow = function(id){ if(this.following.indexOf(id) === -1){ this.following.push(id); } return this.save(); }; UserSchema.methods.unfollow = function(id){ this.following.remove(id); return this.save(); }; UserSchema.methods.isFollowing = function(id){ return this.following.some(function(followId){ return id.toString() === followId.toString(); }); };
Они точно такие же, как и в методах article избранное / unavorite / isFavorite.
Перейдем в routes / api / profiles.js и добавим новые маршруты, которые будут использовать наши недавно созданные методы.
router.post('/:username/follow', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ if(!user){return res.sendStatus(401);} return user.follow(req.profile._id).then(function(){ return res.json({profile: req.profile.toProfileJSONFor(user)}); }); }).catch(next); }); router.delete('/:username/follow', auth.required, function(req,res,next){ User.findById(req.payload.id).then(function(user){ return user.unfollow(req.profile._id).then(function(){ return res.json({profile: req.profile.toProfileJSONFor(user)}); }); }).catch(next); });
Опять же, эти маршруты почти такие же, как в статье для добавления в избранное, поэтому здесь нет ничего нового.
Перейдем в models / User.js и внесем последние изменения, чтобы следующая функция работала:
UserSchema.methods.toProfileJSONFor = function(user){ return { username: this.username, bio: this.bio, image: this.image, /* THIS LINE IS EDITED */following: user ? user.isFollowing(this._id) : false }; };
Edit he / * редактируется эта строка * / только выделенная часть. Все остальное оставьте без изменений.
Проверьте маршруты с помощью Почтальона.
12. Отметить маршрут
В routes / api / создайте новый файл с именем tags.js:
var mongoose = require('mongoose'); var Article = mongoose.model('Article'); var router = require('express').Router(); router.get('/', function(req,res,next){ Article.find().distinct('tagList').then(function(tags){ return res.json({tags: tags}); }).catch(next); }); module.exports = router;
Очень простой файл, который используется для создания маршрута, который будет отображать все теги, используемые в статьях.
Не забудьте добавить его в index.js в той же папке:
var router = require('express').Router(); router.use('/', require('./users')); router.use('/profiles', require('./profiles')); router.use('/articles', require('./articles')); /*new code*/router.use('/tags', require('./tags')); module.exports = router;
Ничего не меняйте, кроме / * нового кода * / строки.
Проверьте маршрут в Почтальоне (GET localhost: 8000 / api / tags) .
13. Запрос статей
В /routes/api/articles.js мы собираемся добавить новый маршрут:
router.get('/', auth.optional, function(req,res,next){ var limit = 20; var offset = 0; var query = {}; if(typeof req.query.limit !== 'undefined'){ limit = req.query.limit; } if(typeof req.query.offset !== 'undefined'){ offset = req.query.offset; } if(typeof req.query.tag !== 'undefined'){ query.tagList = {"$in": [req.query.tag]}; } Promise.all([ req.query.author ? User.findOne({username: req.query.author}) : null, req.query.favorited ? User.findOne({username: req.query.favorited}) : null ]).then(function(results){ var favoriter = results[1]; var author = results[0]; if(author){ query.author = author._id; } if(favoriter){ query._id = {$in: favoriter.favorites}; } else if(req.query.favorited){ query._id = {$in: []}; } return Promise.all([ Article.find(query) .limit(Number(limit)) .skip(Number(offset)) .sort({createdAt: 'desc'}) .populate('author') .exec(), Article.count(query).exec(), req.payload ? User.findById(req.payload.id) : null ]).then(function(results){ var articles = results[0]; var articleCount = results[1]; var user = results[2]; return res.json({ articles: articles.map(function(article){ return article.toJSONFor(user); }), articleCount: articleCount }); }); }).catch(next); });
Не волнуйтесь, это самый длинный маршрут, который вы увидите в этом руководстве, и он совсем не такой сложный. Мы проверяем, был ли запрошен limit, offset или tag (… / article ? Limit = 10,… / article ? Offset = 2,… / article ? Tag = new) и добавьте их в запрос, если они есть.
Затем мы проверяем, был ли запрошен избранный или автор (… / article ? Favorited = user1,… / article ? Author = mark ), а также добавьте их в запрос. Наконец, мы возвращаем asyncpromise, который ограничивает заданным нами limit запросом, пропускает по смещению установленный нами запрос заполняет автора, проверяет, вошел ли в систему пользователь и выполняет его. Затем мы получаем результаты на основе наших запросов (определенных тегов, ограничений, авторов, избранных и т. д.).
Проверьте маршрут и запросы (? X = y) в почтальоне.
14. Корм (последний шаг)
Снова в /routes/api/articles.js добавьте другой маршрут, но убедитесь, что это первый маршрут выше router.post ('/'…) :
router.get('/feed', auth.required, function(req,res,next){ var limit = 20; var query = {}; var offset = 0; if(typeof req.query.limit !== 'undefined'){ limit = req.query.limit; } if(typeof req.query.offset !== 'undefined'){ offset = req.query.offset; } User.findById(req.payload.id).then(function(user){ if(!user){return res.sendStatus(401);} Promise.all([ Article.find({author: {$in: user.following}}) .limit(Number(limit)) .skip(Number(offset)) .populate('author') .exec(), Article.count({author: {$in: user.following}}) ]).then(function(results){ var articles = results[0]; var articleCount = results[1]; return res.json({ articles: articles.map(function(article){ return article.toJSONFor(user); }), articleCount: articleCount }); }); }).catch(next); });
Маршрут почти такой же, как и предыдущий, но на нем отображаются не все статьи, а только авторов, на которых подписан пользователь.
Проверьте маршрут в Почтальоне, сначала обязательно подписывайтесь на того, у кого есть статья!
Бэкэнд завершен!
Поздравляю! Вы закончили работу с моей серверной частью REST API, используя учебник по Express.js! Надеюсь, вы кое-что узнали, и мои инструкции не были слишком ужасными. Спасибо за ваше время, и, к вашему сведению, скоро появится только часть Frontend, касающаяся именно этого внутреннего интерфейса. :)
Антонио Эрдельжак