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

Шаг 1: Установите экспресс и установите проект

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

npm init

Это настроит NPM для вашего проекта, создав файл package.json в корне вашего проекта.

Затем выполните следующие команды, чтобы установить Express и связанные зависимости:

npm install express helmet compression cors xss-clean dotenv joi validator bcryptjs nodemailer winston --save

Помимо того, что вы можете создать HTTP-сервер, зависимости также позволяют сжимать gzip, защищать от разных источников, очищать межсайтовые сценарии, SMTP (отправка электронной почты), проверку полей и ведение журнала.

Создать папку статических документов

В корне вашего проекта создайте папку с именем «public». Внутри вы можете создать файл «index.html», если хотите, загрузить статические документы/файлы и организовать свою центральную сеть распространения. Поместите весь свой клиентский javascript в «общедоступную» папку (предпочтительно в папку «js»). Все статические документы в «общедоступной» папке будут сопоставлены с корневым URL-адресом вашего приложения.

Создать конфигурацию

В корне вашего проекта создайте папку с именем «config», создайте внутри файл с именем «config.js», добавьте следующее:

const dotenv = require('dotenv');
const path = require('path');
const Joi = require('joi');

dotenv.config({ path: path.join(__dirname, '../../.env') });
// Enforce type on the following properties:
const envVarsSchema = Joi.object()
  .keys({
    NODE_ENV: Joi.string().valid('production', 'development', 'test').required(),
    PORT: Joi.number().default(3000),
    DB_HOST: Joi.string().description('database server hostname (usually localhost)'),
    DB_USERNAME: Joi.string().description('username to connect to database'),
    DB_PASSWORD: Joi.string().description('password to connect to database'),
    DB_NAME: Joi.string().description('name of database'),
    SMTP_HOST: Joi.string().description('server that will send the emails'),
    SMTP_PORT: Joi.number().description('port to connect to the email server'),
    SMTP_USERNAME: Joi.string().description('username for email server'),
    SMTP_PASSWORD: Joi.string().description('password for email server'),
    EMAIL_FROM: Joi.string().description('the from field in the emails sent by the app'),
  })
  .unknown();
const { value: envVars, error } = envVarsSchema.prefs({ errors: { label: 'key' } }).validate(process.env);
if (error) {
  throw new Error(`Config validation error: ${error.message}`);
}
// Define Config
module.exports = {
    env: envVars.NODE_ENV,
    port: envVars.PORT,
    db: {
      host: envVars.DB_HOST,
      username: envVars.DB_USERNAME,
      password: envVars.DB_PASSWORD,
      database: envVars.DB_NAME,
      /* Uncomment if using MongoDB
      settings: {
            useNewUrlParser: true,
            useUnifiedTopology: true
      } */
    },
    email: {
      smtp: {
        host: envVars.SMTP_HOST,
        port: envVars.SMTP_PORT,
        auth: {
          user: envVars.SMTP_USERNAME,
          pass: envVars.SMTP_PASSWORD,
        },
      },
      from: envVars.EMAIL_FROM,
   }
};

Затем создайте файл с именем «logger.js» внутри папки «config», это настроит регистратор, добавьте следующее содержимое:

/*
  Logger Config
  @author: hagopj13
  @Source: https://github.com/hagopj13/node-express-boilerplate/blob/master/src/confog/logger.js
*/
const winston = require('winston');
const config = require('./config');

const enumerateErrorFormat = winston.format((info) => {
  if (info instanceof Error) {
    Object.assign(info, { message: info.stack });
  }
  return info;
});
const logger = winston.createLogger({
  level: config.env === 'development' ? 'debug' : 'info',
  format: winston.format.combine(
    enumerateErrorFormat(),
    config.env === 'development' ? winston.format.colorize() : winston.format.uncolorize(),
    winston.format.splat(),
    winston.format.printf(({ level, message }) => `${level}: ${message}`)
  ),
  transports: [
    new winston.transports.Console({
      stderrLevels: ['error'],
    }),
  ],
});
module.exports = logger;

Создать маршруты

В корне вашего проекта создайте папку с именем «routes», создайте внутри файл с именем «index.js», добавьте следующее содержимое:

const express = require('express');
//const myRoute = require('./my.route');
const welcomeRoute = require('./welcome.route');

const config = require('../config/config');
const router = express.Router();
// { path: '/my', route: myRoute }
const defaultRoutes = [
    {
        path: '/',
        route: welcomeRoute
    },
];
defaultRoutes.forEach((route) => {
    router.use(route.path, route.route);
});
module.exports = router;

Затем создайте файл в том же каталоге (маршруты) с именем «welcome.route.js», добавьте следующее содержимое:

const express = require('express');
const welcomeController = require('../../controllers/welcome.controller');
const router = express.Router();

router.route('/').get(welcomeController.example);
module.exports = router;

Шаг 2: Создайте модель

Модель — это компонент архитектуры MVC, который представляет данные и бизнес-логику приложения. В экспресс-приложении модель обычно реализуется как база данных или хранилище данных, например база данных SQL или база данных NoSQL, такая как MongoDB.

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

module.exports.User = require('./user.model');

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

MySQL/MariaDB

Mongoose — это библиотека моделирования объектных данных (ODM) для MongoDB и Node.js. Он предоставляет простое решение на основе схемы для моделирования данных вашего приложения. Он включает в себя встроенное приведение типов, проверку, построение запросов, перехватчики бизнес-логики и многое другое.

Чтобы использовать Mongoose с базой данных MySQL, вам необходимо использовать дополнительную библиотеку, которая позволяет использовать MySQL в качестве источника данных для Mongoose. Одной из таких библиотек является mongoose-mysql-connector.

Вот пример того, как вы можете использовать Mongoose с базой данных MySQL в приложении Node.js и Express:

  • Установите необходимые пакеты:
npm install mongoose mongoose-mysql-connector
  • Добавьте следующий код в index.js для подключения к MySQL:
const mongoose = require('mongoose');
const mysqlConnector = require('mongoose-mysql-connector');

const config = require('../config/config');

let server;
mongoose.connect(mysqlConnector, {
  host: config.db.host,
  user: config.db.username,
  password: config.db.password,
  database: config.db.database
}).then(() => {
  logger.info('Connected to database');
  server = app.listen(config.port, () => {
    logger.info(`Listening to port ${config.port}`);
  });
});

const exitHandler = () => {
  if (server) {
    server.close(() => {
      logger.info('Server closed');
      process.exit(1);
    });
  } else {
    process.exit(1);
  }
};

const unexpectedErrorHandler = (error) => {
  logger.error(error);
  exitHandler();
};

process.on('uncaughtException', unexpectedErrorHandler);
process.on('unhandledRejection', unexpectedErrorHandler);

process.on('SIGTERM', () => {
  logger.info('SIGTERM received');
  if (server) {
    server.close();
  }
});
  • Создайте модель для каждого типа данных, которые вы хотите смоделировать, создайте файл внутри «моделей» с именем «user.model.js», добавьте следующее содержимое:
const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');

const Schema = mongoose.Schema;
const UserSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true,
    unique: true,
    trim: true,
    lowercase: true,
    validate(value) {
      if (!validator.isEmail(value)) {
        throw new Error('Invalid email');
      }
    },
  },
  password: {
    type: String,
    required: true,
    trim: true,
    minlength: 8,
    validate(value) {
      if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
        throw new Error('Password must contain at least one letter and one number');
      }
    }
  },
  isEmailVerified: {
    type: Boolean,
    default: false,
  },
  date: {
    type: Date,
    default: Date.now
  }
});

/**
 * Check if email is taken
 * @param {string} email - The user's email
 * @param {ObjectId} [excludeUserId] - The id of the user to be excluded
 * @returns {Promise<boolean>}
 */
userSchema.statics.isEmailTaken = async function (email, excludeUserId) {
  const user = await this.findOne({ email, _id: { $ne: excludeUserId } });
  return !!user;
};

/**
 * Check if password matches the user's password
 * @param {string} password
 * @returns {Promise<boolean>}
 */
userSchema.methods.isPasswordMatch = async function (password) {
  const user = this;
  return bcrypt.compare(password, user.password);
};

/**
 * @typedef User
 */
const User = mongoose.model('User', userSchema);

module.exports = User;
  • Используйте модель для запроса и управления данными в базе данных MySQL:
// Find all users
User.find({}, (err, users) => {
  if (err) throw err;
  console.log(users);
});

// Create a new user
const newUser = new User({
  name: 'John Doe',
  email: '[email protected]',
  password: 'password123'
});

newUser.save((err) => {
  if (err) throw err;
  console.log('User saved successfully');
});

// Update a user
User.findOneAndUpdate(
  { email: '[email protected]' },
  { name: 'John Smith' },
  (err) => {
    if (err) throw err;
    console.log('User updated successfully');
  }
);

// Delete a user
User.findOneAndDelete({ email: '[email protected]' }, (err) => {
  if (err) throw err;
  console.log('User deleted successfully');
});

MongoDB

Mongoose — это инструмент объектного моделирования MongoDB, предназначенный для работы в асинхронной среде. Он помогает использовать базу данных MongoDB с Node.js, предоставляя простое решение на основе схемы для моделирования данных вашего приложения.

  • Чтобы использовать Mongoose с Node.js и Express, сначала необходимо установить Mongoose с помощью следующей команды:
npm install mongoose
  • Добавьте следующий код в app.js для подключения к MongoDB:
const mongoose = require('mongoose');
const config = require('./config/config');

let server;
mongoose.connect(config.database.host, config.database.settings).then(() => {
  logger.info('Connected to database');
  server = app.listen(config.port, () => {
    logger.info(`Listening to port ${config.port}`);
  });
});

const exitHandler = () => {
  if (server) {
    server.close(() => {
      logger.info('Server closed');
      process.exit(1);
    });
  } else {
    process.exit(1);
  }
};

const unexpectedErrorHandler = (error) => {
  logger.error(error);
  exitHandler();
};

process.on('uncaughtException', unexpectedErrorHandler);
process.on('unhandledRejection', unexpectedErrorHandler);

process.on('SIGTERM', () => {
  logger.info('SIGTERM received');
  if (server) {
    server.close();
  }
});
  • Создайте модель для каждого типа данных, которые вы хотите смоделировать, создайте файл внутри «моделей» с именем «user.model.js», добавьте следующее содержимое:
const userSchema = new mongoose.Schema({
  name: String,
  email: String,
  password: String
});

const User = mongoose.model('User', userSchema);

module.exports = User;
  • Затем вы можете использовать модель User для создания нового пользовательского документа следующим образом:
const user = new User({
  name: 'John',
  email: '[email protected]',
  password: 'password'
});

user.save(function(error) {
  if (error) {
    console.log(error);
  } else {
    console.log('User saved successfully!');
  }
});
  • Вы также можете использовать модель для запроса документов в коллекции и их обновления или удаления. Например, вот как вы можете найти пользователя по его электронной почте и обновить его имя:
User.findOne({ email: '[email protected]' }, function(error, user) {
  if (error) {
    console.log(error);
  } else {
    user.name = 'Jane';
    user.save(function(error) {
      if (error) {
        console.log(error);
      } else {
        console.log('User updated successfully!');
      }
    });
  }
});

Шаг 3: Создайте представление

Представление — это компонент архитектуры MVC, представляющий пользовательский интерфейс приложения. В экспресс-приложении представление обычно реализуется с помощью механизма шаблонов, такого как Pug, EJS или Handlebars.

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

Шаг 4: Создайте контроллер

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

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

Начните с создания папки в корне вашего проекта под названием «контроллеры». Создайте внутри файл с именем «index.js», добавьте следующее содержимое:

// module.exports.myController = require('./my.controller');
module.exports.welcomeController = require('./welcome.controller');

Затем создайте файл внутри «контроллеров» с именем «welcome.controller.js», добавьте следующее содержимое:

//const { emailService } = require('../services');

const example = (req, res) => {
  res.send('Hello World!');
};

Шаг 5. Настройте экспресс-приложение

Чтобы настроить экспресс-приложение, вам нужно создать файл app.js и настроить необходимое промежуточное ПО и маршруты. Вот пример простого экспресс-приложения, которое устанавливает базовую структуру MVC:

app.js

const path = require('path');
const express = require('express');
const helmet = require('helmet');
const xss = require('xss-clean');

const compression = require('compression');
const cors = require('cors');

const config = require('./config/config');

const routes = require('./routes');

const app = express();

app.use(helmet());

app.use(express.json());

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

app.use(xss());
app.use(compression());

app.use(cors());
app.options('*', cors());

app.use('/', routes);
app.use(express.static(path.join(__dirname, 'public')));

// send back a 404 error for any unknown api request
app.use((req, res, next) => {
  next(new ApiError(httpStatus.NOT_FOUND, 'Not found'));
});

module.exports = app;

index.js

const app = require('./app');
const config = require('./config/config');
const logger = require('./config/logger');

app.listen(config.port, () => {
    console.log(`Example app listening on port ${config.port}`)
})

Это приложение настраивает экспресс с промежуточным программным обеспечением для синтаксического анализа тела в формате JSON и URL-кодирования и определяет два маршрута: маршрут GET, который отображает основное представление, и маршрут POST, который создает новый элемент в модели.

Создать услуги

В корне вашего проекта создайте папку под названием «services», мы добавим простую службу электронной почты, чтобы проиллюстрировать, как создавать службы. Создайте внутри файл с именем «index.js», добавьте следующее содержимое:

module.exports.emailService = require('./email.service'); 

Затем создайте внутри файл с именем «email.service.js», добавьте следующее содержимое:

/*
  Email Service
  @author: hagopj13
  @Source: https://github.com/hagopj13/node-express-boilerplate/blob/master/src/services/email.service.js
*/
const nodemailer = require('nodemailer');
const config = require('../config/config');
const logger = require('../config/logger');

const transport = nodemailer.createTransport(config.email.smtp);
/* istanbul ignore next */
if (config.env !== 'test') {
  transport
    .verify()
    .then(() => logger.info('Connected to email server'))
    .catch(() => logger.warn('Unable to connect to email server. Make sure you have configured the SMTP options in .env'));
}

/**
 * Send an email
 * @param {string} to
 * @param {string} subject
 * @param {string} text
 * @returns {Promise}
 */
const sendEmail = async (to, subject, text) => {
  const msg = { from: config.email.from, to, subject, text };
  await transport.sendMail(msg);
};

/**
 * Send reset password email
 * @param {string} to
 * @param {string} token
 * @returns {Promise}
 */
const sendResetPasswordEmail = async (to, token) => {
  const subject = 'Reset password';
  // replace this url with the link to the reset password page of your front-end app
  const resetPasswordUrl = `http://link-to-app/reset-password?token=${token}`;
  const text = `Dear user,
To reset your password, click on this link: ${resetPasswordUrl}
If you did not request any password resets, then ignore this email.`;
  await sendEmail(to, subject, text);
};

/**
 * Send verification email
 * @param {string} to
 * @param {string} token
 * @returns {Promise}
 */
const sendVerificationEmail = async (to, token) => {
  const subject = 'Email Verification';
  // replace this url with the link to the email verification page of your front-end app
  const verificationEmailUrl = `http://link-to-app/verify-email?token=${token}`;
  const text = `Dear user,
To verify your email, click on this link: ${verificationEmailUrl}
If you did not create an account, then ignore this email.`;
  await sendEmail(to, subject, text);
};

module.exports = {
  transport,
  sendEmail,
  sendResetPasswordEmail,
  sendVerificationEmail,
};

Шаг 6. Протестируйте приложение MVC

Чтобы протестировать приложение MVC, запустите сервер, выполнив в терминале следующую команду:

node app.js

Затем перейдите по корневому URL вашего приложения в браузере (например, http://localhost:3000). Вы должны увидеть основное представление своего приложения и иметь возможность создавать новые элементы, отправляя форму или отправляя запрос POST на маршрут /create.

Скачать стартовый проект

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