从零到店面:我的电子商务网站建设之旅
内容
- 简介
- 技术堆栈
- 快速概述
- API
- 前端
- 管理仪表板
- 资源
源代码:https://github.com/aelassas/wexcommerce
演示:https://wexcommerce.dynv6.net:8002
介绍
对于重视创意自由和技术控制的开发者来说,Shopify 等传统电子商务平台可能会感到受到限制。虽然 Shopify 的模板提供了快速设置,并且其 Storefront API 提供了一定的灵活性,但这两种解决方案都没有提供现代开发人员渴望的完整架构自由。
这个想法源于建立无边界的愿望 - 一个完全可定制的电子商务网站,其中每个方面都在您的控制之下:
- 拥有 UI/UX:设计独特的客户体验,而无需克服模板限制
- 控制后端:实现完美匹配需求的自定义业务逻辑和数据结构
- 掌握 DevOps:使用首选工具和工作流程部署、扩展和监控应用程序
- 自由扩展:添加新功能和集成,无需平台限制或额外费用
技术堆栈
这是使其成为可能的技术堆栈:
- Node.js
- Next.js
- MongoDB
- MUI
- 打字稿
- 条纹
- 码头工人
由于 TypeScript 具有众多优点,因此做出了使用 TypeScript 的关键设计决定。 TypeScript 提供强大的类型、工具和集成,从而产生高质量、可扩展、更具可读性和可维护性的代码,并且易于调试和测试。
我选择Next.js是因为它强大的渲染功能,MongoDB是为了灵活的数据建模,而Stripe是为了安全的支付处理。
通过选择此堆栈,您不仅仅是在构建一个商店 - 您是在投资一个可以根据您的需求不断发展的基础,并得到强大的开源技术和不断发展的开发者社区的支持。
使用 Next.js 构建网站为扩展业务奠定了坚实的基础。关注性能、安全性和用户体验,同时维护代码质量和文档。定期更新和监控将确保平台保持竞争力和可靠性。
Next.js 因其以下优点而成为绝佳选择:
- 卓越性能:内置优化,可实现快速页面加载和无缝用户体验
- SEO 优势:服务器端渲染功能,确保您的产品易于被发现
- 可扩展性:随您的业务一起成长的企业就绪架构
- 丰富的生态系统:用于快速开发的大量库和工具
- 开发者体验:直观的开发工作流程,具有热重载和自动路由
快速概览
前端
从前端,用户可以搜索可用产品、将产品添加到购物车并结帐。
下面是前端的登陆页面:
下面是前端的搜索页面:
以下是示例产品页面:
以下是产品图片的全屏视图:
以下是购物车页面:
以下是结账页面:
以下是登录页面:
以下是注册页面:
下面是用户可以看到他的订单的页面:
就是这样!这些是前端的主要页面。
管理仪表板
通过管理仪表板,管理员可以管理类别、产品、用户和订单。
管理员还可以管理以下设置:
- 区域设置:平台语言(英语或法语)和货币
- 配送设置:启用的配送方式以及每种方式的费用
- 付款设置:启用付款方式(信用卡、货到付款或电汇)
- 银行设置:电汇的银行信息(IBAN 和其他信息)
以下是登录页面:
下面是管理员可以查看和管理订单的仪表板页面:
以下是管理员管理类别的页面:
以下是管理员可以查看和管理产品的页面:
以下是管理员编辑产品的页面:
以下是产品图片的全屏视图:
以下是设置页面:
就是这样。这些是管理仪表板的主要页面。
应用程序编程接口
API 是一个 Node.js 服务器应用程序,它使用 Express 公开 RESTful API,从而可以访问 MongoDB 数据库。
API 由前端、管理仪表板使用,也将由移动应用程序使用。
API 公开了管理仪表板和前端所需的所有功能。该API遵循MVC设计模式。 JWT 用于身份验证。有些功能需要身份验证,例如与管理产品和订单相关的功能,而其他功能则不需要身份验证,例如为未经过身份验证的用户检索类别和可用产品:
- ./api/src/models/ 文件夹包含 MongoDB 模型。
- ./api/src/routes/ 文件夹包含 Express 路线。
- ./api/src/controllers/ 文件夹包含控制器。
- ./api/src/middlewares/ 文件夹包含中间件。
- ./api/src/app.ts 是加载路由的主服务器。
- ./api/src/index.ts 是 api 的主要入口点。
index.ts 在主服务器中:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import * as env from './config/env.config' import * as databaseHelper from './common/databaseHelper' import app from './app' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
这是一个使用 Node.js 和 Express 启动服务器的 TypeScript 文件。它导入了多个模块,包括 dotenv、process、fs、http、https、mongoose 和 app。然后它与 MongoDB 数据库建立连接。然后,它检查 HTTPS 环境变量是否设置为 true,如果是,则使用 https 模块以及提供的私钥和证书创建 HTTPS 服务器。否则,它使用 http 模块创建一个 HTTP 服务器。服务器监听 PORT 环境变量中指定的端口。
close 函数被定义为在收到终止信号时优雅地停止服务器。它关闭服务器和 MongoDB 连接,然后以状态码 0 退出进程。最后,它注册当进程收到 SIGINT、SIGTERM 或 SIGQUIT 信号时要调用的 close 函数。
app.ts 是 api 的主要入口点:
import express from 'express' import compression from 'compression' import helmet from 'helmet' import nocache from 'nocache' import cookieParser from 'cookie-parser' import i18n from './lang/i18n' import * as env from './config/env.config' import cors from './middlewares/cors' import allowedMethods from './middlewares/allowedMethods' import userRoutes from './routes/userRoutes' import categoryRoutes from './routes/categoryRoutes' import productRoutes from './routes/productRoutes' import cartRoutes from './routes/cartRoutes' import orderRoutes from './routes/orderRoutes' import notificationRoutes from './routes/notificationRoutes' import deliveryTypeRoutes from './routes/deliveryTypeRoutes' import paymentTypeRoutes from './routes/paymentTypeRoutes' import settingRoutes from './routes/settingRoutes' import stripeRoutes from './routes/stripeRoutes' import wishlistRoutes from './routes/wishlistRoutes' import * as helper from './common/helper' const app = express() app.use(helmet.contentSecurityPolicy()) app.use(helmet.dnsPrefetchControl()) app.use(helmet.crossOriginEmbedderPolicy()) app.use(helmet.frameguard()) app.use(helmet.hidePoweredBy()) app.use(helmet.hsts()) app.use(helmet.ieNoOpen()) app.use(helmet.noSniff()) app.use(helmet.permittedCrossDomainPolicies()) app.use(helmet.referrerPolicy()) app.use(helmet.xssFilter()) app.use(helmet.originAgentCluster()) app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' })) app.use(helmet.crossOriginOpenerPolicy()) app.use(nocache()) app.use(compression({ threshold: 0 })) app.use(express.urlencoded({ limit: '50mb', extended: true })) app.use(express.json({ limit: '50mb' })) app.use(cors()) app.options('*', cors()) app.use(cookieParser(env.COOKIE_SECRET)) app.use(allowedMethods) app.use('/', userRoutes) app.use('/', categoryRoutes) app.use('/', productRoutes) app.use('/', cartRoutes) app.use('/', orderRoutes) app.use('/', notificationRoutes) app.use('/', deliveryTypeRoutes) app.use('/', paymentTypeRoutes) app.use('/', settingRoutes) app.use('/', stripeRoutes) app.use('/', wishlistRoutes) i18n.locale = env.DEFAULT_LANGUAGE await helper.mkdir(env.CDN_USERS) await helper.mkdir(env.CDN_TEMP_USERS) await helper.mkdir(env.CDN_CATEGORIES) await helper.mkdir(env.CDN_TEMP_CATEGORIES) await helper.mkdir(env.CDN_PRODUCTS) await helper.mkdir(env.CDN_TEMP_PRODUCTS) export default app
首先,我们创建一个 Express 应用并加载 cors、compression、helmet 和 nocache 等中间件。我们使用头盔中间件库设置了各种安全措施。我们还为应用程序的不同部分导入各种路由文件,例如productRoutes、orderRoutes、categoryRoutes、notificationRoutes、userRoutes。最后,我们加载 Express 路线并导出应用程序。
api中有11条路由。每条路线都有自己的控制器,遵循 MVC 设计模式和 SOLID 原则。主要路线如下:
- userRoutes:提供与用户相关的REST功能
- categoryRoutes:提供与类别相关的REST函数
- productRoutes:提供与产品相关的REST功能
- cartRoutes:提供与购物车相关的REST功能
- wishlistRoutes:提供与愿望清单相关的REST功能
- deliveryTypeRoutes:提供与配送方式相关的REST功能
- paymentTypeRoutes:提供与支付方式相关的REST函数
- orderRoutes:提供与订单相关的REST函数
- notificationRoutes:提供与通知相关的REST功能
- settingRoutes:提供与设置相关的REST功能
- stripeRoutes:提供与Stripe支付网关相关的REST功能
我们不会一一解释每条路线。我们将以categoryRoutes为例,看看它是如何制作的:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import * as env from './config/env.config' import * as databaseHelper from './common/databaseHelper' import app from './app' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
首先,我们创建一个 Express Router。然后,我们使用它的名称、方法、中间件和控制器来创建路由。
routeNames 包含categoryRoutes 路线名称:
import express from 'express' import compression from 'compression' import helmet from 'helmet' import nocache from 'nocache' import cookieParser from 'cookie-parser' import i18n from './lang/i18n' import * as env from './config/env.config' import cors from './middlewares/cors' import allowedMethods from './middlewares/allowedMethods' import userRoutes from './routes/userRoutes' import categoryRoutes from './routes/categoryRoutes' import productRoutes from './routes/productRoutes' import cartRoutes from './routes/cartRoutes' import orderRoutes from './routes/orderRoutes' import notificationRoutes from './routes/notificationRoutes' import deliveryTypeRoutes from './routes/deliveryTypeRoutes' import paymentTypeRoutes from './routes/paymentTypeRoutes' import settingRoutes from './routes/settingRoutes' import stripeRoutes from './routes/stripeRoutes' import wishlistRoutes from './routes/wishlistRoutes' import * as helper from './common/helper' const app = express() app.use(helmet.contentSecurityPolicy()) app.use(helmet.dnsPrefetchControl()) app.use(helmet.crossOriginEmbedderPolicy()) app.use(helmet.frameguard()) app.use(helmet.hidePoweredBy()) app.use(helmet.hsts()) app.use(helmet.ieNoOpen()) app.use(helmet.noSniff()) app.use(helmet.permittedCrossDomainPolicies()) app.use(helmet.referrerPolicy()) app.use(helmet.xssFilter()) app.use(helmet.originAgentCluster()) app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' })) app.use(helmet.crossOriginOpenerPolicy()) app.use(nocache()) app.use(compression({ threshold: 0 })) app.use(express.urlencoded({ limit: '50mb', extended: true })) app.use(express.json({ limit: '50mb' })) app.use(cors()) app.options('*', cors()) app.use(cookieParser(env.COOKIE_SECRET)) app.use(allowedMethods) app.use('/', userRoutes) app.use('/', categoryRoutes) app.use('/', productRoutes) app.use('/', cartRoutes) app.use('/', orderRoutes) app.use('/', notificationRoutes) app.use('/', deliveryTypeRoutes) app.use('/', paymentTypeRoutes) app.use('/', settingRoutes) app.use('/', stripeRoutes) app.use('/', wishlistRoutes) i18n.locale = env.DEFAULT_LANGUAGE await helper.mkdir(env.CDN_USERS) await helper.mkdir(env.CDN_TEMP_USERS) await helper.mkdir(env.CDN_CATEGORIES) await helper.mkdir(env.CDN_TEMP_CATEGORIES) await helper.mkdir(env.CDN_PRODUCTS) await helper.mkdir(env.CDN_TEMP_PRODUCTS) export default app
categoryController 包含有关类别的主要业务逻辑。我们不会看到控制器的所有源代码,因为它相当大,但我们将以创建控制器函数为例。
以下是类别模型:
import express from 'express' import multer from 'multer' import routeNames from '../config/categoryRoutes.config' import authJwt from '../middlewares/authJwt' import * as categoryController from '../controllers/categoryController' const routes = express.Router() routes.route(routeNames.validate).post(authJwt.verifyToken, categoryController.validate) routes.route(routeNames.checkCategory).get(authJwt.verifyToken, categoryController.checkCategory) routes.route(routeNames.create).post(authJwt.verifyToken, categoryController.create) routes.route(routeNames.update).put(authJwt.verifyToken, categoryController.update) routes.route(routeNames.delete).delete(authJwt.verifyToken, categoryController.deleteCategory) routes.route(routeNames.getCategory).get(authJwt.verifyToken, categoryController.getCategory) routes.route(routeNames.getCategories).get(categoryController.getCategories) routes.route(routeNames.getFeaturedCategories).get(categoryController.getFeaturedCategories) routes.route(routeNames.searchCategories).get(authJwt.verifyToken, categoryController.searchCategories) routes.route(routeNames.createImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], categoryController.createImage) routes.route(routeNames.updateImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], categoryController.updateImage) routes.route(routeNames.deleteImage).post(authJwt.verifyToken, categoryController.deleteImage) routes.route(routeNames.deleteTempImage).post(authJwt.verifyToken, categoryController.deleteTempImage) export default routes
一个类别有多个值。每种语言一个值。默认支持英语和法语。
以下是价值模型:
export default { validate: '/api/validate-category', checkCategory: '/api/check-category/:id', create: '/api/create-category', update: '/api/update-category/:id', delete: '/api/delete-category/:id', getCategory: '/api/category/:id/:language', getCategories: '/api/categories/:language/:imageRequired', getFeaturedCategories: '/api/featured-categories/:language/:size', searchCategories: '/api/search-categories/:language', createImage: '/api/create-category-image', updateImage: '/api/update-category-image/:id', deleteImage: '/api/delete-category-image/:id', deleteTempImage: '/api/delete-temp-category-image/:image', }
值具有语言代码 (ISO 639-1) 和字符串值。
下面是创建控制器函数:
import { Schema, model } from 'mongoose' import * as env from '../config/env.config' const categorySchema = new Schema<env.Category>({ values: { type: [Schema.Types.ObjectId], ref: 'Value', validate: (value: any) => Array.isArray(value), }, image: { type: String, }, featured: { type: Boolean, default: false, }, }, { timestamps: true, strict: true, collection: 'Category', }) const Category = model<env.Category>('Category', categorySchema) export default Category
在此函数中,我们检索请求的正文,迭代正文中提供的值(每种语言一个值)并创建一个值。最后,我们根据创建的值和图像文件创建类别。
前端
前端是一个使用 Next.js 和 MUI 构建的 Web 应用程序。从前端,用户可以搜索可用产品,将其添加到购物车,然后根据可用的交付和付款方式继续结账。
- ./frontend/public/ 文件夹包含公共资源。
- ./frontend/src/styles/ 文件夹包含 CSS 样式。
- ./frontend/src/components/ 文件夹包含 React 组件。
- ./frontend/src/lang/ 包含语言环境文件。
- ./frontend/src/app/ 文件夹包含 Next.js 页面。
- ./frontend/src/lib/ 包含服务器操作。
- ./frontend/next.config.ts 是前端的主要配置文件。
前端是使用 create-next-app 创建的:
import { Schema, model } from 'mongoose' import * as env from '../config/env.config' const locationValueSchema = new Schema<env.Value>( { language: { type: String, required: [true, "can't be blank"], index: true, trim: true, lowercase: true, minLength: 2, maxLength: 2, }, value: { type: String, required: [true, "can't be blank"], index: true, trim: true, }, }, { timestamps: true, strict: true, collection: 'Value', }, ) const Value = model<env.Value>('Value', locationValueSchema) export default Value
在Next.js中,页面是从pages目录中的.js、.jsx、.ts或.tsx文件导出的React组件。每个页面都根据其文件名与一条路径相关联。
默认情况下,Next.js 预渲染每个页面。这意味着 Next.js 提前为每个页面生成 HTML,而不是全部由客户端 JavaScript 完成。预渲染可以带来更好的性能和 SEO。
每个生成的 HTML 都与该页面所需的最少 JavaScript 代码相关联。当浏览器加载页面时,其 JavaScript 代码就会运行并使页面完全交互。 (这个过程称为水合作用。)
前端使用服务器端渲染进行SEO优化,以便产品可以被搜索引擎索引。
管理仪表板
管理仪表板是一个使用 Next.js 和 MUI 构建的 Web 应用程序。通过管理仪表板,管理员可以管理类别、产品、订单和用户。创建新订单时,管理员用户会收到通知并收到电子邮件。
- ./backend/public/ 文件夹包含公共资产。
- ./backend/src/styles/ 文件夹包含 CSS 样式。
- ./backend/src/components/ 文件夹包含 React 组件。
- ./backend/src/lang/ 包含语言环境文件。
- ./backend/src/app/ 文件夹包含 Next.js 页面。
- ./backend/src/lib/ 包含服务器操作。
- ./backend/next.config.ts 是后端的主要配置文件。
管理仪表板也是使用 create-next-app 创建的:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import * as env from './config/env.config' import * as databaseHelper from './common/databaseHelper' import app from './app' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
资源
- 概述
- 安装(自托管)
-
安装(Docker)
- Docker 镜像
- SSL
- 设置条纹
- 从源头运行
-
演示数据库
- Windows、Linux 和 macOS
- 码头工人
- 更改语言和货币
- 添加新语言
- 单元测试和覆盖率
- 日志
就是这样!我希望你喜欢阅读。
以上是从零到店面:我的电子商务网站建设之旅的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务
