像bit.ly和cutt.ly这样的URL缩短器是非常流行的。在这篇文章中,我们将通过建立一个缩短提供给它的URL的API服务来创建一个类似的工具。

在这个项目中,我们将使用MongoDB和Node.js,所以你应该有关于它们的基本知识来学习这个教程。
在Node.js中规划URL缩短器的构建过程
让我们首先规划一下构建过程,这是很简单的。对于传入我们的API的每个URL,我们将生成一个唯一的ID,并用它创建一个短URL。然后,长URL、短URL和唯一ID将被存储在数据库中。
当用户向短网址发送一个GET ,该网址将在数据库中被搜索到,用户将被重定向到相应的原始网址。听起来很复杂?别担心,我们会涵盖你需要知道的一切。
用MongoDB初始化应用程序和安装依赖性
首先,我们将需要一个数据库。因为我们将使用MongoDB,我们需要一个MongoDB SRV URI。你可以从这个链接创建一个数据库。我们的下一步是用NPM初始化项目文件夹。
让我们使用项目目录下的命令npm init 来初始化。一旦项目被初始化,我们将安装所需的依赖项。我们需要的依赖性是。
dotenv:这个包从一个叫做.env的文件中加载环境变量,然后再加载到 。process.envexpress:这是一个用于Node.js的最小而灵活的Web应用框架mongoose: 这是一个用于Node.js的MongoDB对象建模工具shortid:这个包使我们能够为我们的URL生成短的ID。
我们唯一需要的开发者依赖性是nodemon 。nodemon 工具是一个简单的工具,可以在文件发生变化时自动重新启动Node.js服务器。
现在,让我们来安装这些依赖项。为了安装我们的应用程序中需要的依赖项,我们将使用命令npm i dotenv express mongoose shortid ,在依赖项安装完毕后,我们将用npm i -D nodemon 安装开发者依赖项。
让我们使用Express在我们的app.js 文件中创建我们的服务器。为了建立一个Express服务器,我们需要将Express包导入到app.js 文件中。一旦软件包被导入,初始化并将其存储到一个名为app 的变量中。
现在,使用可用的listen 函数来创建服务器。这里有一个例子。
const Express = require('Express');
const app = Express();
// Server Setup
const PORT = 3333;
app.listen(PORT, () => {
console.log(`Server is running at PORT ${PORT}`);
});
我使用了端口3333 来运行服务器。Express中的listen 方法启动了一个UNIX套接字,并在一个给定的端口中监听连接。
现在,在config 文件夹中创建一个.env 文件,以存储 MongoDB SRV URI 和基本 URL。基本URL现在将是你的本地主机服务器位置。下面是我的.env 文件代码。
MONGO_URI=mongodb+srv://nemo:YourPasswordHere@cluster0.mkws3.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
BASE=http://localhost:3333
记住用你的数据库密码改变MongoDB URI中的<password> 字段。
将数据库连接到应用程序
现在,我们将把数据库连接到应用程序。为此,将mongoose 和dotenv 依赖项导入你的db.js 文件,该文件位于config 文件夹内。
const mongoose = require('mongoose');
require('dotenv').config({ path: './.env' });
path 的对象键是在dotenv 的配置里面传递的,因为.env 文件不在根目录下。我们正在通过这个传递.env 文件的位置。
现在在config 文件夹内的一个名为db.js 的文件中创建一个名为connectDB 的异步函数。在这篇文章中,我将使用async/await 。
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('Database Connected');
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
module.exports = connectDB;
在try 块中,我们等待mongoose 与给定的MongoDB URI连接。mongoose.connect 方法的第一个参数是MongoDB SRV URI。注意在第二个参数中传递了两个键值对,以消除控制台的警告。让我们来理解这两个键值参数的含义。
useNewUrlParser: true:MongoDB底层驱动程序已经废弃了当前的连接字符串解析器。这就是为什么它增加了一个新的标志。如果连接遇到新的字符串解析器的任何问题,它可以退回到旧的解析器useUnifiedTopology: true: 默认设置为false。在这里,它被设置为true,以便可以使用MongoDB驱动的新连接管理引擎
如果在catch 语句中出现任何错误,我们将在控制台记录错误并以process.exit(1) 退出。最后,我们用module.exports 输出该函数。
现在,用const connectDB = require('./config/db'); 把db.js 文件导入到app.js 文件中,用connectDB() 调用connectDB 函数。
在MongoDB中创建mongoose模式
我们将使用mongoose模式来确定数据在MongoDB中的存储方式。从本质上讲,mongoose模式是数据的一个模型。让我们在models 文件夹内创建一个名为Url.js 的文件。在这里导入mongoose ,然后使用mongoose.Schema 构造函数来创建模式。
const mongoose = require('mongoose');
const UrlSchema = new mongoose.Schema({
urlId: {
type: String,
required: true,
},
origUrl: {
type: String,
required: true,
},
shortUrl: {
type: String,
required: true,
},
clicks: {
type: Number,
required: true,
default: 0,
},
date: {
type: String,
default: Date.now,
},
});
module.exports = mongoose.model('Url', UrlSchema);
父对象的键是将被存储在数据库里面的键。我们定义每个数据键。注意,有些键有一个必填字段,其他键有一个默认值。
最后,我们使用module.exports = mongoose.model('Url', UrlSchema); 输出模式。mongoose.model 里面的第一个参数是要存储的数据的单数形式,第二个参数是模式本身。
建立URL和索引路由
URL路由将从原始URL创建一个短的URL,并将其存储在数据库中。在根目录下创建一个名为routes 的文件夹,并在其中创建一个名为urls.js 的文件。我们将在这里使用Express路由器。首先,导入所有必要的包,像这样。
const Express = require('express');
const router = Express.Router();
const shortid = require('shortid');
const Url = require('../models/Url');
const utils = require('../utils/utils');
require('dotenv').config({ path: '../config/.env' });
utils 文件夹内的utils.js 文件由一个函数组成,用于检查传递的 URL 是否有效。下面是utils.js 文件的代码。
function validateUrl(value) {
return /^(?:(?:(?:https?|ftp):)?\\/\\/)(?:\\S+(?::\\S*)?@)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))(?::\\d{2,5})?(?:[/?#]\\S*)?$/i.test(
value
);
}
module.exports = { validateUrl };
我们将使用urls.js 文件中的HTTP发布请求来生成并发布细节到数据库。
const Express = require('express');
const router = Express.Router();
const shortid = require('shortid');
const Url = require('../models/Url');
const utils = require('../utils/utils');
require('dotenv').config({ path: '../config/.env' });
// Short URL Generator
router.post('/short', async (req, res) => {
const { origUrl } = req.body;
const base = process.env.BASE;
const urlId = shortid.generate();
if (utils.validateUrl(origUrl)) {
try {
let url = await Url.findOne({ origUrl });
if (url) {
res.json(url);
} else {
const shortUrl = `${base}/${urlId}`;
url = new Url({
origUrl,
shortUrl,
urlId,
date: new Date(),
});
await url.save();
res.json(url);
}
} catch (err) {
console.log(err);
res.status(500).json('Server Error');
}
} else {
res.status(400).json('Invalid Original Url');
}
});
module.exports = router;
const { origUrl } = req.body; 将从HTTP请求体中提取origUrl 的值。然后我们将基本的URL存储到一个变量中。const urlId = shortid.generate(); 是生成并存储一个短的ID到一个变量中。
一旦它被生成,我们使用我们的函数从utils 目录中检查原始URL是否有效。对于有效的URL,我们进入try 块。
在这里,我们首先用Url.findOne({ origUrl }); mongoose方法搜索原始URL是否已经存在于我们的数据库中。如果找到了,我们以JSON格式返回数据;否则,我们创建一个结合基本URL和短ID的短URL。
然后,使用我们的mongoose模型,我们将字段传递给模型构造函数,并用url.save(); 方法将其保存到数据库中。保存后,我们以JSON格式返回响应。
try 区块的意外错误在catch 区块中处理,无效的URL在我们的validateUrl 函数中返回false ,就会发回URL无效的信息。最后,我们导出路由器。
以前,我们需要安装body-parser 包,但现在它已经集成到Express中了,所以回到app.js 文件,添加这两行来使用body-parser 。
// Body Parser
app.use(Express.urlencoded({ extended: true }));
app.use(Express.json());
这两行帮助我们读取传入的请求。在这两行代码之后,导入URL路由。
app.use('/api', require('./routes/urls'));
因为我们使用的是/api 端点,所以我们的完整端点变成了 [http://localhost:3333/api/short](http://localhost:3333/api/short).下面是一个例子。
现在在routes 文件夹中创建另一个名为index.js 的文件,以处理重定向过程。在这个文件中,导入必要的依赖项。
在这里,我们首先要在我们的数据库中搜索传来的短的URL ID。如果找到了这个URL,我们将重定向到原来的URL。
const Express = require('express');
const router = Express.Router();
const Url = require('../models/Url');
router.get('/:urlId', async (req, res) => {
try {
const url = await Url.findOne({ urlId: req.params.urlId });
if (url) {
url.clicks++;
url.save();
return res.redirect(url.origUrl);
} else res.status(404).json('Not found');
} catch (err) {
console.log(err);
res.status(500).json('Server Error');
}
});
module.exports = router;
HTTPGET 请求是在:urlId 的帮助下获得URL ID。然后,在try 块内,我们使用Url.findOne 方法找到URL,与我们在urls.js 路由中的做法类似。
如果找到了URL,我们就增加对该URL的点击次数并保存点击量。最后,我们使用return res.redirect(url.origUrl); ,将用户重定向到原始URL。
如果没有找到URL,我们会发送一个JSON消息说没有找到URL。任何未捕获的异常都在catch 块中处理。我们在控制台记录错误,并发送一个 "服务器错误 "的JSON消息。最后,我们导出路由器。
将路由导入到app.js 文件中,我们的URL缩短器就可以使用了。导入后,我们最终的app.js 文件将看起来像这样。
const Express = require('Express');
const app = Express();
const connectDB = require('./config/db');
require('dotenv').config({ path: './config/.env' });
connectDB();
// Body Parser
app.use(Express.urlencoded({ extended: true }));
app.use(Express.json());
app.use('/', require('./routes/index'));
app.use('/api', require('./routes/urls'));
// Server Setup
const PORT = 3333;
app.listen(PORT, () => {
console.log(`Server is running at PORT ${PORT}`);
});
结论
在这篇文章中,我们学习了如何从头开始建立一个URL缩短服务API。你可以将它与任何你想要的前端集成,甚至建立一个全栈的URL缩短服务。我希望你喜欢阅读这篇文章,并在阅读过程中学习到一些新的东西。你可以在我的GitHub repo上找到完整的源代码。
The postBuilding a URL shortener with Node.jsappeared first onLogRocket Blog.