소스 코드: https://github.com/aelassas/wexcommerce
데모: https://wexcommerce.dynv6.net:8002
창의적 자유와 기술적 통제를 중시하는 개발자의 경우 Shopify와 같은 기존 전자상거래 플랫폼이 제한적이라고 느낄 수 있습니다. Shopify의 템플릿은 빠른 설정을 제공하고 Storefront API는 어느 정도 유연성을 제공하지만 두 솔루션 모두 현대 개발자가 갈망하는 완전한 아키텍처 자유를 제공하지 않습니다.
이 아이디어는 경계 없이 모든 측면을 제어할 수 있는 완전 맞춤형 전자상거래 사이트를 구축하려는 열망에서 탄생했습니다.
이를 가능하게 한 기술 스택은 다음과 같습니다.
TypeScript의 수많은 장점 때문에 주요 디자인 결정이 TypeScript를 사용하기로 결정되었습니다. TypeScript는 강력한 타이핑, 도구 및 통합을 제공하여 디버그 및 테스트가 쉬운 고품질, 확장성, 읽기 쉽고 유지 관리가 쉬운 코드를 제공합니다.
저는 강력한 렌더링 기능을 위해 Next.js를 선택했고, 유연한 데이터 모델링을 위해 MongoDB를, 안전한 결제 처리를 위해 Stripe를 선택했습니다.
이 스택을 선택하면 단순히 매장을 짓는 것이 아니라 강력한 오픈 소스 기술과 성장하는 개발자 커뮤니티를 기반으로 필요에 따라 발전할 수 있는 기반에 투자하는 것입니다.
Next.js로 사이트를 구축하면 비즈니스 확장을 위한 견고한 기반이 제공됩니다. 코드 품질과 문서화를 유지하면서 성능, 보안, 사용자 경험에 집중하세요. 정기적인 업데이트와 모니터링을 통해 플랫폼의 경쟁력과 안정성을 유지할 수 있습니다.
Next.js는 다음과 같은 이유로 탁월한 선택입니다.
프런트엔드에서 사용자는 사용 가능한 제품을 검색하고 장바구니에 제품을 추가하고 결제할 수 있습니다.
아래는 프론트엔드 랜딩 페이지입니다.
아래는 프론트엔드 검색 페이지입니다.
아래는 샘플 제품 페이지입니다.
아래는 제품 이미지 전체 화면입니다.
아래는 장바구니 페이지입니다:
아래는 결제 페이지입니다:
아래는 로그인 페이지입니다:
아래는 회원가입 페이지입니다.
다음은 사용자가 주문한 내용을 확인할 수 있는 페이지입니다.
그렇습니다! 프론트엔드의 메인 페이지입니다.
관리자 대시보드에서 관리자는 카테고리, 제품, 사용자, 주문을 관리할 수 있습니다.
관리자는 다음 설정도 관리할 수 있습니다.
아래는 로그인 페이지입니다:
다음은 관리자가 주문을 확인하고 관리할 수 있는 대시보드 페이지입니다.
아래는 관리자가 카테고리를 관리하는 페이지입니다.
아래는 관리자가 상품을 확인하고 관리할 수 있는 페이지입니다.
아래는 관리자가 제품을 편집하는 페이지입니다.
아래는 제품 이미지 전체 화면입니다.
아래는 설정 페이지입니다:
그렇습니다. 관리자 대시보드의 메인 페이지입니다.
API는 MongoDB 데이터베이스에 대한 액세스를 제공하는 Express를 사용하여 RESTful API를 노출하는 Node.js 서버 애플리케이션입니다.
API는 프런트엔드, 관리 대시보드에서 사용되며 모바일 앱에서도 사용됩니다.
API는 관리 대시보드와 프런트엔드에 필요한 모든 기능을 노출합니다. API는 MVC 디자인 패턴을 따릅니다. 인증에는 JWT가 사용됩니다. 제품 및 주문 관리와 관련된 기능과 같이 인증이 필요한 기능도 있고, 인증되지 않은 사용자의 카테고리 및 사용 가능한 제품 검색과 같이 인증이 필요하지 않은 기능도 있습니다.
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 환경 변수에 지정된 포트에서 수신 대기합니다.
종료 신호가 수신되면 서버를 정상적으로 중지하도록 닫기 기능이 정의되어 있습니다. 서버와 MongoDB 연결을 닫은 후 상태 코드 0으로 프로세스를 종료합니다. 마지막으로 프로세스가 SIGINT, SIGTERM 또는 SIGQUIT 신호를 수신할 때 호출될 닫기 함수를 등록합니다.
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, 압축, 헬멧, nocache 등의 미들웨어를 로드합니다. 헬멧 미들웨어 라이브러리를 이용하여 다양한 보안 대책을 마련했습니다. 또한 productRoutes, orderRoutes, CategoryRoutes,notificationRoutes, userRoutes와 같은 애플리케이션의 다양한 부분에 대한 다양한 경로 파일을 가져옵니다. 마지막으로 Express 경로를 로드하고 앱을 내보냅니다.
API에는 11개의 경로가 있습니다. 각 경로에는 MVC 디자인 패턴과 SOLID 원칙을 따르는 자체 컨트롤러가 있습니다. 주요 경로는 다음과 같습니다.
각 경로를 하나씩 설명하지는 않겠습니다. 예를 들어, 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로 구축된 웹 애플리케이션입니다. 프런트엔드에서 사용자는 사용 가능한 제품을 검색하여 장바구니에 추가하고 사용 가능한 배송 및 결제 방법에 따라 결제를 진행할 수 있습니다.
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에서 페이지는 페이지 디렉터리의 .js, .jsx, .ts 또는 .tsx 파일에서 내보낸 React 구성 요소입니다. 각 페이지는 파일 이름을 기반으로 경로와 연결됩니다.
기본적으로 Next.js는 모든 페이지를 사전 렌더링합니다. 이는 Next.js가 클라이언트 측 JavaScript로 모든 작업을 수행하는 대신 각 페이지에 대해 미리 HTML을 생성한다는 것을 의미합니다. 사전 렌더링을 통해 성능과 SEO가 향상될 수 있습니다.
생성된 각 HTML은 해당 페이지에 필요한 최소한의 JavaScript 코드와 연결됩니다. 브라우저가 페이지를 로드하면 해당 JavaScript 코드가 실행되어 페이지가 완전히 대화형으로 만들어집니다. (이 과정을 수분 공급이라고 합니다.)
프런트엔드는 검색 엔진에서 제품을 색인화할 수 있도록 SEO 최적화를 위해 서버 측 렌더링을 사용합니다.
관리 대시보드는 Next.js 및 MUI로 구축된 웹 애플리케이션입니다. 관리자 대시보드에서 관리자는 카테고리, 제품, 주문 및 사용자를 관리할 수 있습니다. 새 주문이 생성되면 관리자에게 알림과 이메일이 전송됩니다.
관리 대시보드도 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)) }
그렇습니다! 재미있게 읽으셨기를 바랍니다.
위 내용은 제로에서 매장까지: 전자상거래 사이트를 구축하는 나의 여정의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!