Quellcode: https://github.com/aelassas/wexcommerce
Demo: https://wexcommerce.dynv6.net:8002
Für Entwickler, die kreative Freiheit und technische Kontrolle schätzen, können traditionelle E-Commerce-Plattformen wie Shopify einschränkend wirken. Während die Vorlagen von Shopify eine schnelle Einrichtung ermöglichen und ihre Storefront-API eine gewisse Flexibilität bietet, bietet keine der beiden Lösungen die vollständige architektonische Freiheit, die sich moderne Entwickler wünschen.
Die Idee entstand aus dem Wunsch heraus, ohne Grenzen zu bauen – eine vollständig anpassbare E-Commerce-Site, bei der jeder Aspekt in Ihrer Kontrolle liegt:
Hier ist der Tech-Stack, der es möglich gemacht hat:
Eine wichtige Designentscheidung wurde aufgrund seiner zahlreichen Vorteile für die Verwendung von TypeScript getroffen. TypeScript bietet starke Typisierung, Tools und Integration, was zu hochwertigem, skalierbarem, besser lesbarem und wartbarem Code führt, der einfach zu debuggen und zu testen ist.
Ich habe mich für Next.js wegen seiner leistungsstarken Rendering-Funktionen, MongoDB für die flexible Datenmodellierung und Stripe für die sichere Zahlungsabwicklung entschieden.
Wenn Sie sich für diesen Stack entscheiden, bauen Sie nicht nur einen Shop auf – Sie investieren in eine Grundlage, die sich mit Ihren Bedürfnissen weiterentwickeln kann, unterstützt durch robuste Open-Source-Technologien und eine wachsende Entwickler-Community.
Der Aufbau einer Website mit Next.js bietet eine solide Grundlage für die Skalierung eines Unternehmens. Konzentrieren Sie sich auf Leistung, Sicherheit und Benutzererfahrung und bewahren Sie gleichzeitig die Codequalität und Dokumentation. Regelmäßige Updates und Überwachung stellen sicher, dass die Plattform wettbewerbsfähig und zuverlässig bleibt.
Next.js zeichnet sich durch Folgendes als ausgezeichnete Wahl aus:
Vom Frontend aus kann der Benutzer nach verfügbaren Produkten suchen, Produkte in den Warenkorb legen und zur Kasse gehen.
Unten ist die Landingpage des Frontends:
Unten ist die Suchseite des Frontends:
Unten finden Sie eine Beispielproduktseite:
Unten finden Sie eine Vollbildansicht der Produktbilder:
Unten ist die Warenkorbseite:
Unten finden Sie die Checkout-Seite:
Unten finden Sie die Anmeldeseite:
Unten finden Sie die Anmeldeseite:
Unten ist die Seite, auf der der Benutzer seine Bestellungen sehen kann:
Das ist es! Das sind die Hauptseiten des Frontends.
Über das Admin-Dashboard können Administratoren Kategorien, Produkte, Benutzer und Bestellungen verwalten.
Administratoren können auch die folgenden Einstellungen verwalten:
Unten finden Sie die Anmeldeseite:
Unten finden Sie die Dashboard-Seite, auf der Administratoren Bestellungen sehen und verwalten können:
Unten finden Sie die Seite, auf der Administratoren Kategorien verwalten:
Unten finden Sie die Seite, auf der Administratoren Produkte sehen und verwalten können:
Unten finden Sie die Seite, auf der Administratoren Produkte bearbeiten:
Unten finden Sie eine Vollbildansicht der Produktbilder:
Unten finden Sie die Einstellungsseite:
Das ist es. Das sind die Hauptseiten des Admin-Dashboards.
Die API ist eine Node.js-Serveranwendung, die mithilfe von Express eine RESTful-API bereitstellt, die Zugriff auf die MongoDB-Datenbank ermöglicht.
Die API wird vom Frontend, dem Admin-Dashboard und auch von der mobilen App verwendet.
Die API stellt alle Funktionen bereit, die für das Admin-Dashboard und das Frontend benötigt werden. Die API folgt dem MVC-Entwurfsmuster. Zur Authentifizierung wird JWT verwendet. Es gibt einige Funktionen, die eine Authentifizierung erfordern, wie zum Beispiel Funktionen im Zusammenhang mit der Verwaltung von Produkten und Bestellungen, und andere, die keine Authentifizierung erfordern, wie zum Beispiel das Abrufen von Kategorien und verfügbaren Produkten für nicht authentifizierte Benutzer:
index.ts befindet sich auf dem Hauptserver:
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)) }
Dies ist eine TypeScript-Datei, die einen Server mit Node.js und Express startet. Es importiert mehrere Module, darunter dotenv, Process, fs, http, https, mongoose und app. Anschließend wird eine Verbindung mit der MongoDB-Datenbank hergestellt. Anschließend prüft es, ob die HTTPS-Umgebungsvariable auf „true“ gesetzt ist, und erstellt in diesem Fall mithilfe des https-Moduls und des bereitgestellten privaten Schlüssels und Zertifikats einen HTTPS-Server. Andernfalls wird mithilfe des http-Moduls ein HTTP-Server erstellt. Der Server lauscht auf dem Port, der in der Umgebungsvariablen PORT angegeben ist.
Die Schließfunktion ist so definiert, dass sie den Server ordnungsgemäß stoppt, wenn ein Beendigungssignal empfangen wird. Es schließt den Server und die MongoDB-Verbindung und beendet dann den Prozess mit dem Statuscode 0. Schließlich registriert es die Schließfunktion, die aufgerufen werden soll, wenn der Prozess ein SIGINT-, SIGTERM- oder SIGQUIT-Signal empfängt.
app.ts ist der Haupteinstiegspunkt der 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
Zuerst erstellen wir eine Express-App und laden Middlewares wie Cors, Kompression, Helm und Nocache. Mithilfe der Helm-Middleware-Bibliothek haben wir verschiedene Sicherheitsmaßnahmen eingerichtet. Wir importieren auch verschiedene Routendateien für verschiedene Teile der Anwendung, wie z. B. „productRoutes“, „orderRoutes“, „categoryRoutes“, „notificationRoutes“ und „userRoutes“. Zum Schluss laden wir Express-Routen und exportieren die App.
Es gibt 11 Routen in der API. Jede Route verfügt über einen eigenen Controller, der dem MVC-Entwurfsmuster und den SOLID-Prinzipien folgt. Nachfolgend sind die Hauptrouten aufgeführt:
Wir werden nicht jede Route einzeln erklären. Wir nehmen zum Beispiel die KategorieRouten und sehen, wie sie erstellt wurde:
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)) }
Zuerst erstellen wir einen Express-Router. Dann erstellen wir Routen unter Verwendung ihres Namens, ihrer Methode, Middleware und ihres Controllers.
routeNames enthält die Routennamen der KategorieRoutes:
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 enthält die Hauptgeschäftslogik in Bezug auf Kategorien. Wir werden nicht den gesamten Quellcode des Controllers sehen, da er recht umfangreich ist, aber wir nehmen zum Beispiel die Funktion zum Erstellen eines Controllers.
Unten finden Sie das Kategoriemodell:
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
Eine Kategorie hat mehrere Werte. Ein Wert pro Sprache. Standardmäßig werden Englisch und Französisch unterstützt.
Unten ist das Wertmodell:
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', }
Ein Wert hat einen Sprachcode (ISO 639-1) und einen Zeichenfolgenwert.
Unten finden Sie die Funktion zum Erstellen eines Controllers:
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
In dieser Funktion rufen wir den Hauptteil der Anfrage ab, durchlaufen die im Hauptteil bereitgestellten Werte (ein Wert pro Sprache) und erstellen einen Wert. Abschließend erstellen wir die Kategorie abhängig von den erstellten Werten und der Bilddatei.
Das Frontend ist eine Webanwendung, die mit Next.js und MUI erstellt wurde. Über das Frontend kann der Benutzer nach verfügbaren Produkten suchen, diese in den Warenkorb legen und je nach verfügbaren Liefer- und Zahlungsmethoden zur Kasse gehen.
Das Frontend wurde mit create-next-app:
erstellt
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
In Next.js ist eine Seite eine React-Komponente, die aus einer .js-, .jsx-, .ts- oder .tsx-Datei im Seitenverzeichnis exportiert wird. Jede Seite ist anhand ihres Dateinamens einer Route zugeordnet.
Standardmäßig rendert Next.js jede Seite vorab. Das bedeutet, dass Next.js im Voraus HTML für jede Seite generiert, anstatt alles durch clientseitiges JavaScript erledigen zu lassen. Vorab-Rendering kann zu einer besseren Leistung und SEO führen.
Jeder generierte HTML-Code ist mit dem minimalen JavaScript-Code verknüpft, der für diese Seite erforderlich ist. Wenn eine Seite vom Browser geladen wird, wird deren JavaScript-Code ausgeführt und macht die Seite vollständig interaktiv. (Dieser Vorgang wird als Hydratation bezeichnet.)
Das Frontend nutzt Server-Side Rendering zur SEO-Optimierung, damit Produkte von Suchmaschinen indiziert werden können.
Das Admin-Dashboard ist eine Webanwendung, die mit Next.js und MUI erstellt wurde. Über das Admin-Dashboard können Administratoren Kategorien, Produkte, Bestellungen und Benutzer verwalten. Wenn eine neue Bestellung erstellt wird, erhält der Admin-Benutzer eine Benachrichtigung und eine E-Mail.
Das Admin-Dashboard wurde ebenfalls mit create-next-app erstellt:
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)) }
Das ist es! Ich hoffe, die Lektüre hat Ihnen gefallen.
Das obige ist der detaillierte Inhalt vonVon Null bis Storefront: Meine Reise zum Aufbau einer E-Commerce-Site. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!