Для разработчика программного обеспечения или инженера создание проектов — один из способов продемонстрировать рост, независимо от вашего уровня. Это также способ придать уверенности и повысить свои навыки.

В этой статье мы будем создавать средство сокращения URL-адресов с использованием Nodejs и Express. Этот проект подходит для начинающих и требует предварительных знаний в области программирования и javascript. Давайте начнем.

Требования

Что нужно для реализации проекта:

  1. Рабочий компьютер
  2. Vscode или любой другой редактор кода на ваш выбор (скачать vscode здесь)
  3. Nodejs (скачать nodejs здесь)
  4. Аккаунт в MongoDB (Как создать аккаунт)

Процесс

  1. Создайте папку, которая будет содержать проект, и откройте папку в редакторе кода.
  2. В редакторе кода откройте терминал и введите npm init, чтобы создать шаблон наших проектов, появятся некоторые вопросы, но мы оставим для них ответы по умолчанию, нажав Enter, для description и author вы можете ввести описание проекта и ваше имя соответственно. В итоге у вас будет файл package.json, который выглядит так
{
  "name": "url-shortner",
  "version": "1.0.0",
  "description": "url-shortner server project",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "favalcodes",
  "license": "ISC",
}

3. Мы собираемся упорядочить нашу файловую структуру и установить некоторые зависимости, во-первых, давайте создадим точку входа в наш проект, создадим папку в корневом проекте и назовем ее src , откройте папку и создайте три папки внутри папки src, а именно models , middlewares и controllers также создайте файл и назовите его server.js, это будет наша главная точка входа, в которой будет размещаться наш сервер. пока что у нас должно быть что-то вроде

--url-shortner
    --src
      --controllers
      --middlewares
      --models
      server.js
    package.json

4. Давайте продолжим загрузку некоторых зависимостей:

  • Nodemon: этот пакет помогает перезапустить проект всякий раз, когда есть изменения, без необходимости перезапуска вручную, он предназначен для использования только на этапе разработки, поэтому мы собираемся установить его как зависимость разработчика, используя npm i -D nodemon
  • Express: В начале этой статьи я упомянул, что мы собираемся использовать nodejs и Express для создания нашего проекта. Express — это минималистичная и гибкая платформа веб-приложений Node.js, которая предоставляет надежный набор функций для веб-приложений и мобильных приложений, npm i express
  • Mongoose: Mongoose — это инструмент объектного моделирования MongoDB, предназначенный для работы в асинхронной среде, для установки npm i mongoose
  • Cors: это пакет node.js для предоставления промежуточного программного обеспечения Express, которое можно использовать для включения общего доступа к ресурсам между источниками (CORS) с различными параметрами для установки npm i cors
  • Nanoid: это пакет для генерации случайных буквенно-цифровых строк для установки npm i nanoid
  • Validate.js: это пакет, используемый для проверки, для установки npm i validate.js
  • Express Async Handler: это пакет простого промежуточного программного обеспечения для обработки исключений внутри асинхронных экспресс-маршрутов и передачи их вашим экспресс-обработчикам ошибок для установки npm i express-async-handler
  • Dotenv: это пакет, который загружает переменные среды из файла .env в process.env для установки npm i dotenv

В конце установки отредактируйте файл package.json, сотрите тестовый скрипт и замените его новым скриптом, который поможет запустить наш сервер, "dev”: nodemon src/server.js, наш файл package.json будет выглядеть так после редактирования

{
  "name": "url-shortner-server",
  "version": "1.0.0",
  "description": "a url shortner server application",
  "main": "src/server.js",
  "scripts": {
    "dev": "nodemon src/server.js"
  },
  "author": "favalcodes",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "express-async-handler": "^1.2.0",
    "mongoose": "^7.4.0",
    "nanoid": "^3.3.6",
    "validate.js": "^0.13.1"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

5. Откройте файл server.js и вставьте его

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
require('dotenv').config()


const app = express()
const corsOptions = {
    origin: process.env.WEB_URL,
    optionsSuccessStatus: 200
  }

app.use(cors(corsOptions))
app.use(bodyParser.urlencoded({ extended: true}))
app.use(bodyParser.json())

app.listen(process.env.PORT, () => {
    console.log(`server started on ${process.env.APP_URL}:${process.env.PORT}`)
})

Приведенный выше блок кода настраивает базовый сервер Express.js с некоторым промежуточным ПО для обработки HTTP-запросов, используя app.use(), настраивает промежуточное ПО. Body-parser используется для анализа тела запроса в формате JSON, он также прослушивает порт, который извлекается из файла .env, и выходит из сервера, запущенного на выделенном URL-адресе и порту.

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

После успешного подключения мы можем приступить к подключению нашего проекта к нашей базе данных. (Примечание: не забудьте добавить URL-адрес MongoDB в наш файл .env).

7. Создайте новый файл в корневой папке нашего проекта и назовите его database.js, этот файл будет содержать наше соединение с базой данных, вставьте приведенный ниже код в файл.

const mongoose = require('mongoose')

const connectDb = (url) => {
    try {
        return mongoose.connect(url)
    } catch (error) {
        return new Error(error)
    }
}

module.exports = connectDb

В приведенном выше коде мы подключаемся к нашей базе данных через Mongoose, используя URL-адрес, который передается в функцию connectDb, мы экспортировали нашу функцию, чтобы использовать ее где-то еще.

8. Мы отредактируем наш файл server.js для подключения к нашей базе данных после запуска нашего сервера,

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const connectDb = require('./database')
require('dotenv').config()


const app = express()
connectDb(process.env.MONGO_URL)
const corsOptions = {
    origin: process.env.WEB_URL,
    optionsSuccessStatus: 200
  }

app.use(cors(corsOptions))
app.use(bodyParser.urlencoded({ extended: true}))
app.use(bodyParser.json())

app.listen(process.env.PORT, () => {
    console.log(`server started on ${process.env.APP_URL}:${process.env.PORT}`)
})

9. Пришло время создать нашу схему БД, создайте новый файл в папке моделей и назовите его url.js, вставьте следующий код

const mongoose = require("mongoose");

const urlSchema = new mongoose.Schema(
  {
    url: { type: String, required: true },
    shortUrl: { type: String, required: true },
    urlKey: { type: String, required: true },
  },
  {
    timestamps: true,
  }
);

const urlModel = mongoose.model("Url", urlSchema);

module.exports = urlModel;

наш URL-документ будет содержать URL-адрес (исходный URL-адрес), преобразованный shortUrl и urlKey.

10. В папке нашего контроллера создайте новый файл и назовите его urlController.js и вставьте следующий код

const urlModel = require("../models/Urls")
const { validateUrl } = require("../validator/urlValidator")
const {customAlphabet} = require('nanoid')
const asyncHandler = require('express-async-handler')


const shortenUrl = asyncHandler(async (req, res) => {
    try {
        const { url } = req.body
        await validateUrl(url)
        const shortId = customAlphabet('1234567890abcdef', 10)
        const urlKey = shortId()
        const shortUrl = `${process.env.WEB_URL}/${urlKey}`
        await urlModel.create({ url, shortUrl, urlKey})
        res.status(200).json({ message: 'Url shortened', url: shortUrl})
    } catch (error) {
        res.status(500)
        throw new Error(error)
    }
})

const getUrl = asyncHandler(async (req, res) => {
    try {
        const urlKey = req.params.url
        const isExist = await urlModel.findOne({urlKey})
        if (!isExist) {
            res.status(404)
            throw new Error('Url does not exist')
        }
        res.redirect(isExist.url)
    } catch (error) {
        res.status(500)
        throw new Error(error)
    }
})

module.exports = {shortenUrl, getUrl}

В приведенном выше коде мы использовали express-async-handler, мы также создали две функции:
a. shortenUrl: эта функция получает URL-адрес, который необходимо сократить, проверяет URL-адрес, чтобы убедиться, что мы работаем с URL-адресом, затем создает ключ URL-адреса с помощью nanoid и продолжает добавлять ключ к URL-адресу приложения или сервера для создайте новый URL-адрес, затем мы сохраним URL-адрес, вновь созданный URL-адрес и ключ URL-адреса в нашу базу данных. в случае успеха он возвращает сообщение об успехе, иначе он возвращает ошибку.

б. getUrl: эта функция перенаправляет пользователя на исходный URL-адрес с использованием urlKey, если urlKey существует, если нет, выдает ошибку.

В приведенном выше коде мы импортировали файл, который еще не создали, давайте продолжим и создадим файл.

11. В папке валидаторов создайте файл и назовите его urlValidator.js и вставьте следующий код

const validate = require('validate.js')

const validateUrl = async (url) => {
    return validate({website: url}, {website: {url: true}});
}

module.exports = { validateUrl }

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

12. Пришло время создать наши маршруты, чтобы иметь доступ к нашим функциям, создайте файл в корневой папке проекта и назовите его routes.js и вставьте следующий код.

const express = require('express')
const { shortenUrl } = require('./controllers/urlController')

const router = express.Router()
router.post('/shorten-url', shortenUrl)

module.exports = router

13. Добавляем маршруты в server.js, наш файл выглядит так

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const connectDb = require('./database')
const router = require('./routes')
const { getUrl } = require('./controller/urlController')
require('dotenv').config()


const app = express()
connectDb(process.env.MONGO_URL)
const corsOptions = {
    origin: process.env.WEB_URL,
    optionsSuccessStatus: 200
  }

app.use(cors(corsOptions))
app.use(bodyParser.urlencoded({ extended: true}))
app.use(bodyParser.json())
app.get('/:url', getUrl)
app.use('/api', require('./routes'))

app.listen(process.env.PORT, () => {
    console.log(`server started on ${process.env.APP_URL}:${process.env.PORT}`)
})

Наш сервер готов на 90%, последнее, что нужно обрабатывать, это ошибки, нам нужно отформатировать наши ошибки, чтобы их могли прочитать пользователи.

14. В папке нашего валидатора создадим новый файл, назовем его errorHandler.js и вставим следующий код.

const { constants } = require("../constants");

const errorHandler = (err, req, res, next) => {
 const statusCode = res.statusCode || 500
 switch (statusCode) {
    case constants.VALIDATION_ERROR:
        res.json({title: 'Validation Error', message: err.message, stackTrace: err.stack})
        break;
    case constants.NOT_FOUND:
        res.json({title: 'Not Found', message: err.message, stackTrace: err.stack})
        break;
    case constants.UNAUTHORIZED:
        res.json({title: 'Unauthorized', message: err.message, stackTrace: err.stack})
        break;
    case constants.FORBIDDEN:
        res.json({title: 'Forbidden', message: err.message, stackTrace: err.stack})
        break;
    case constants.SERVER_ERROR:
        res.json({title: 'Server error', message: err.message, stackTrace: err.stack})
        break;
 
    default:
        break;
 }
}

module.exports = errorHandler

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

exports.constants = {
    NOT_FOUND: 404,
    VALIDATION_ERROR: 400,
    UNAUTHORIZED: 401,
    FORBIDDEN: 403,
    SERVER_ERROR: 500
}

Мы собираемся отредактировать наш server.js, чтобы обрабатывать errorHandler в качестве промежуточного программного обеспечения.

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const connectDb = require('./database')
const router = require('./routes')
const { getUrl } = require('./controllers/urlController')
const errorHandler = require('./validator/errorHandler')
require('dotenv').config()


const app = express()
connectDb(process.env.MONGO_URL)
const corsOptions = {
    origin: process.env.WEB_URL,
    optionsSuccessStatus: 200
  }

app.use(cors(corsOptions))
app.use(bodyParser.urlencoded({ extended: true}))
app.use(bodyParser.json())
app.get('/:url', getUrl)
app.use('/api', require('./routes'))
app.use(errorHandler)

app.listen(process.env.PORT, () => {
    console.log(`server started on ${process.env.APP_URL}:${process.env.PORT}`)
})

Мы, наконец, закончили создание нашего сервера 🥳🥳🥳, вы можете продолжить и протестировать свой API с помощью своего инструмента тестирования, я использую Postman для тестирования своих API.

Если у вас есть вопросы или у вас есть блокировщик, не забудьте написать в разделе комментариев, я буду рад помочь 😃.