Shopify应用开发完整指南
Shopify应用开发是扩展平台功能的强大方式。本指南将带您深入了解Shopify应用的开发流程、最佳实践和高级技术。
应用开发基础
1. 应用类型选择
公开应用(Public Apps)
- 在Shopify应用商店分发
- 需要通过Shopify审核
- 使用OAuth认证
- 支持多商店安装
私有应用(Private Apps)
- 仅限特定商店使用
- 直接通过Admin API访问
- 简化的认证流程
- 适合定制化需求
自定义应用(Custom Apps)
- 商店所有者自行创建
- 替代私有应用的新方案
- 更严格的权限控制
2. 开发环境搭建
# 安装Shopify CLI
npm install -g @shopify/cli @shopify/theme
# 创建新应用
shopify app init my-shopify-app
# 选择技术栈
# - Node.js + Express
# - Next.js + TypeScript
# - Ruby on Rails
# - PHP + Laravel
项目结构:
my-shopify-app/
├── app/
│ ├── routes/
│ ├── models/
│ └── services/
├── frontend/
│ ├── components/
│ ├── pages/
│ └── hooks/
├── extensions/
│ ├── theme-extension/
│ └── checkout-ui-extension/
├── web/
│ ├── index.js
│ └── middleware/
└── shopify.app.toml
认证和权限管理
1. OAuth 2.0实现
// 认证流程实现
const express = require('express')
const crypto = require('crypto')
const app = express()
// 安装URL生成
app.get('/auth', (req, res) => {
const shop = req.query.shop
const scopes = 'read_products,write_products,read_orders'
const redirectUri = `${process.env.APP_URL}/auth/callback`
const state = crypto.randomBytes(32).toString('hex')
// 保存state用于验证
req.session.state = state
const authUrl = `https://${shop}.myshopify.com/admin/oauth/authorize?` +
`client_id=${process.env.SHOPIFY_API_KEY}&` +
`scope=${scopes}&` +
`redirect_uri=${redirectUri}&` +
`state=${state}`
res.redirect(authUrl)
})
// 回调处理
app.get('/auth/callback', async (req, res) => {
const { code, hmac, shop, state } = req.query
// 验证HMAC
const query = new URLSearchParams(req.query).toString()
const calculatedHmac = crypto
.createHmac('sha256', process.env.SHOPIFY_API_SECRET)
.update(query.replace(`&hmac=${hmac}`, ''))
.digest('hex')
if (calculatedHmac !== hmac) {
return res.status(401).send('Unauthorized')
}
// 验证state
if (state !== req.session.state) {
return res.status(401).send('State mismatch')
}
// 获取访问令牌
try {
const tokenResponse = await fetch(`https://${shop}.myshopify.com/admin/oauth/access_token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.SHOPIFY_API_KEY,
client_secret: process.env.SHOPIFY_API_SECRET,
code
})
})
const tokenData = await tokenResponse.json()
// 保存访问令牌
await saveAccessToken(shop, tokenData.access_token)
res.redirect(`/app?shop=${shop}`)
} catch (error) {
res.status(500).send('Authentication failed')
}
})
2. 权限检查中间件
// 权限检查中间件
const checkPermissions = (requiredScopes) => {
return async (req, res, next) => {
const shop = req.query.shop || req.headers['x-shopify-shop-domain']
const token = await getAccessToken(shop)
try {
// 验证当前权限
const response = await fetch(`https://${shop}.myshopify.com/admin/api/2023-10/access_scopes.json`, {
headers: {
'X-Shopify-Access-Token': token
}
})
const data = await response.json()
const currentScopes = data.access_scopes.map(scope => scope.handle)
// 检查是否有所需权限
const hasPermission = requiredScopes.every(scope =>
currentScopes.includes(scope)
)
if (!hasPermission) {
return res.status(403).json({
error: 'Insufficient permissions',
required: requiredScopes,
current: currentScopes
})
}
req.shopifyToken = token
next()
} catch (error) {
res.status(401).json({ error: 'Authentication failed' })
}
}
}
// 使用权限检查
app.get('/api/products',
checkPermissions(['read_products']),
async (req, res) => {
// 产品数据处理
}
)
GraphQL API应用
1. 高级查询示例
// 复杂产品查询
const PRODUCTS_QUERY = `
query getProducts($first: Int!, $after: String, $query: String) {
products(first: $first, after: $after, query: $query) {
edges {
node {
id
handle
title
description
status
vendor
productType
createdAt
updatedAt
tags
priceRangeV2 {
minVariantPrice {
amount
currencyCode
}
maxVariantPrice {
amount
currencyCode
}
}
variants(first: 250) {
edges {
node {
id
title
sku
price
compareAtPrice
inventoryQuantity
inventoryPolicy
selectedOptions {
name
value
}
image {
url
altText
}
}
}
}
images(first: 10) {
edges {
node {
url
altText
width
height
}
}
}
metafields(first: 250) {
edges {
node {
id
namespace
key
value
type
}
}
}
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`
// 执行查询
async function fetchProducts(shop, accessToken, variables = {}) {
const response = await fetch(`https://${shop}.myshopify.com/admin/api/2023-10/graphql.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': accessToken
},
body: JSON.stringify({
query: PRODUCTS_QUERY,
variables: {
first: 50,
...variables
}
})
})
const data = await response.json()
if (data.errors) {
throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`)
}
return data.data.products
}
2. 批量操作和变更
// 批量产品更新
const BULK_PRODUCT_UPDATE = `
mutation bulkOperationRunMutation($mutation: String!) {
bulkOperationRunMutation(mutation: $mutation) {
bulkOperation {
id
status
errorCode
createdAt
completedAt
objectCount
fileSize
url
type
}
userErrors {
field
message
}
}
}
`
// 构建批量更新操作
async function bulkUpdateProducts(shop, accessToken, updates) {
const mutations = updates.map(update =>
`productUpdate(input: {
id: "${update.id}",
title: "${update.title}",
descriptionHtml: "${update.description}",
status: ${update.status}
}) {
product { id }
userErrors { field message }
}`
).join('\n')
const bulkMutation = `
mutation {
${mutations}
}
`
const response = await fetch(`https://${shop}.myshopify.com/admin/api/2023-10/graphql.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': accessToken
},
body: JSON.stringify({
query: BULK_PRODUCT_UPDATE,
variables: { mutation: bulkMutation }
})
})
return await response.json()
}
Webhook处理
1. Webhook验证和处理
const crypto = require('crypto')
// Webhook验证中间件
const verifyWebhook = (req, res, next) => {
const hmac = req.get('X-Shopify-Hmac-Sha256')
const body = req.body
const hash = crypto
.createHmac('sha256', process.env.SHOPIFY_WEBHOOK_SECRET)
.update(body, 'utf8')
.digest('base64')
if (hash !== hmac) {
return res.status(401).send('Unauthorized')
}
next()
}
// 订单创建Webhook
app.post('/webhooks/orders/create',
express.raw({ type: 'application/json' }),
verifyWebhook,
async (req, res) => {
try {
const order = JSON.parse(req.body)
// 处理新订单
await processNewOrder(order)
// 发送通知
await sendOrderNotification(order)
// 更新库存
await updateInventory(order.line_items)
res.status(200).send('OK')
} catch (error) {
console.error('Order webhook error:', error)
res.status(500).send('Error processing order')
}
}
)
// 产品更新Webhook
app.post('/webhooks/products/update',
express.raw({ type: 'application/json' }),
verifyWebhook,
async (req, res) => {
try {
const product = JSON.parse(req.body)
// 同步产品数据到外部系统
await syncProductToExternalSystem(product)
// 更新搜索索引
await updateSearchIndex(product)
// 触发价格规则检查
await checkPricingRules(product)
res.status(200).send('OK')
} catch (error) {
console.error('Product webhook error:', error)
res.status(500).send('Error processing product update')
}
}
)
2. Webhook注册管理
// 动态注册Webhook
async function registerWebhooks(shop, accessToken) {
const webhooks = [
{
topic: 'orders/create',
address: `${process.env.APP_URL}/webhooks/orders/create`,
format: 'json'
},
{
topic: 'orders/updated',
address: `${process.env.APP_URL}/webhooks/orders/update`,
format: 'json'
},
{
topic: 'products/update',
address: `${process.env.APP_URL}/webhooks/products/update`,
format: 'json'
},
{
topic: 'app/uninstalled',
address: `${process.env.APP_URL}/webhooks/app/uninstalled`,
format: 'json'
}
]
for (const webhook of webhooks) {
try {
const response = await fetch(`https://${shop}.myshopify.com/admin/api/2023-10/webhooks.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': accessToken
},
body: JSON.stringify({ webhook })
})
const data = await response.json()
console.log(`Webhook registered: ${webhook.topic}`, data.webhook?.id)
} catch (error) {
console.error(`Failed to register webhook: ${webhook.topic}`, error)
}
}
}
应用扩展开发
1. 主题扩展
{% comment %}
Product Reviews Block
Accepts:
- product: product object
- show_rating: boolean
- max_reviews: number
{% endcomment %}
<div class="product-reviews" data-product-id="{{ product.id }}">
{% if settings.show_rating %}
<div class="reviews-summary">
<div class="rating-stars" data-rating="{{ product.metafields.reviews.average_rating }}">
{% for i in (1..5) %}
<span class="star {% if i <= product.metafields.reviews.average_rating %}filled{% endif %}">★</span>
{% endfor %}
</div>
<span class="reviews-count">({{ product.metafields.reviews.count }} reviews)</span>
</div>
{% endif %}
<div class="reviews-list" id="reviews-{{ product.id }}">
<!-- Reviews will be loaded via JavaScript -->
</div>
{% if settings.allow_reviews %}
<button class="write-review-btn" data-product-id="{{ product.id }}">
Write a Review
</button>
{% endif %}
</div>
<script>
// Load reviews via AJAX
fetch(`/apps/reviews/api/products/${{{ product.id }}}/reviews`)
.then(response => response.json())
.then(reviews => {
const container = document.getElementById('reviews-{{ product.id }}')
container.innerHTML = reviews.map(review => `
<div class="review">
<div class="review-header">
<span class="reviewer-name">${review.author}</span>
<div class="review-rating">${'★'.repeat(review.rating)}</div>
</div>
<p class="review-content">${review.content}</p>
</div>
`).join('')
})
</script>
{% schema %}
{
"name": "Product Reviews",
"target": "section",
"stylesheet": "product-reviews.css",
"javascript": "product-reviews.js",
"settings": [
{
"type": "checkbox",
"id": "show_rating",
"label": "Show rating summary",
"default": true
},
{
"type": "number",
"id": "max_reviews",
"label": "Maximum reviews to show",
"default": 5
},
{
"type": "checkbox",
"id": "allow_reviews",
"label": "Allow customers to write reviews",
"default": true
}
]
}
{% endschema %}
2. 结账UI扩展
// extensions/checkout-ui-extension/src/Checkout.tsx
import React, { useState, useEffect } from 'react'
import {
useApi,
useTranslate,
reactExtension,
Banner,
BlockStack,
Checkbox,
Text,
useCartLines,
useApplyCartLinesChange,
} from '@shopify/ui-extensions-react/checkout'
export default reactExtension(
'purchase.checkout.block.render',
() => <ExtensionComponent />
)
function ExtensionComponent() {
const translate = useTranslate()
const { extension } = useApi()
const cartLines = useCartLines()
const applyCartLinesChange = useApplyCartLinesChange()
const [isGiftWrap, setIsGiftWrap] = useState(false)
const [giftMessage, setGiftMessage] = useState('')
// 检查是否已有礼品包装
useEffect(() => {
const giftWrapLine = cartLines.find(line =>
line.merchandise.sku === 'GIFT-WRAP'
)
setIsGiftWrap(!!giftWrapLine)
}, [cartLines])
const handleGiftWrapChange = async (checked) => {
setIsGiftWrap(checked)
if (checked) {
// 添加礼品包装
await applyCartLinesChange({
type: 'addCartLine',
merchandiseId: 'gid://shopify/ProductVariant/GIFT_WRAP_VARIANT_ID',
quantity: 1,
attributes: [
{ key: 'Gift Message', value: giftMessage }
]
})
} else {
// 移除礼品包装
const giftWrapLine = cartLines.find(line =>
line.merchandise.sku === 'GIFT-WRAP'
)
if (giftWrapLine) {
await applyCartLinesChange({
type: 'removeCartLine',
id: giftWrapLine.id,
quantity: giftWrapLine.quantity
})
}
}
}
return (
<BlockStack border="dotted" padding="tight">
<Banner title="Gift Options">
<BlockStack>
<Checkbox
checked={isGiftWrap}
onChange={handleGiftWrapChange}
>
Add gift wrapping (+$5.00)
</Checkbox>
{isGiftWrap && (
<TextField
label="Gift message (optional)"
value={giftMessage}
onChange={setGiftMessage}
multiline={3}
/>
)}
<Text size="small" appearance="subdued">
Gift wrapping includes premium wrapping paper and a handwritten note.
</Text>
</BlockStack>
</Banner>
</BlockStack>
)
}
数据管理和同步
1. 数据库设计
// models/Shop.js
const mongoose = require('mongoose')
const shopSchema = new mongoose.Schema({
domain: { type: String, required: true, unique: true },
accessToken: { type: String, required: true },
scope: { type: String, required: true },
country: String,
currency: String,
timezone: String,
planName: String,
// 应用设置
settings: {
enableReviews: { type: Boolean, default: true },
autoSync: { type: Boolean, default: true },
notificationEmail: String,
customFields: [String]
},
// 同步状态
lastSync: Date,
syncStatus: {
type: String,
enum: ['idle', 'syncing', 'error'],
default: 'idle'
},
// 使用统计
usage: {
apiCalls: { type: Number, default: 0 },
lastApiCall: Date,
monthlyApiCalls: { type: Number, default: 0 },
resetDate: Date
},
// 安装/卸载时间
installedAt: { type: Date, default: Date.now },
uninstalledAt: Date
}, {
timestamps: true
})
// 索引
shopSchema.index({ domain: 1 })
shopSchema.index({ installedAt: 1 })
shopSchema.index({ 'usage.monthlyApiCalls': 1 })
module.exports = mongoose.model('Shop', shopSchema)
2. 数据同步服务
// services/SyncService.js
class SyncService {
constructor(shop, accessToken) {
this.shop = shop
this.accessToken = accessToken
this.batchSize = 100
}
async syncAllProducts() {
try {
await this.updateSyncStatus('syncing')
let hasNextPage = true
let cursor = null
let totalSynced = 0
while (hasNextPage) {
const result = await this.fetchProductsBatch(cursor)
// 处理产品数据
await this.processProductsBatch(result.products)
totalSynced += result.products.length
hasNextPage = result.pageInfo.hasNextPage
cursor = result.pageInfo.endCursor
// 进度报告
await this.reportProgress(totalSynced)
// 避免API限制
await this.sleep(200)
}
await this.updateSyncStatus('idle')
await this.updateLastSync()
return { success: true, synced: totalSynced }
} catch (error) {
await this.updateSyncStatus('error')
throw error
}
}
async fetchProductsBatch(cursor) {
const query = `
query getProducts($first: Int!, $after: String) {
products(first: $first, after: $after) {
edges {
node {
id
handle
title
status
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`
const response = await fetch(`https://${this.shop}.myshopify.com/admin/api/2023-10/graphql.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': this.accessToken
},
body: JSON.stringify({
query,
variables: {
first: this.batchSize,
after: cursor
}
})
})
const data = await response.json()
if (data.errors) {
throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`)
}
return {
products: data.data.products.edges.map(edge => edge.node),
pageInfo: data.data.products.pageInfo
}
}
async processProductsBatch(products) {
const operations = products.map(product => ({
updateOne: {
filter: { shopifyId: product.id },
update: {
$set: {
shopifyId: product.id,
handle: product.handle,
title: product.title,
status: product.status,
updatedAt: new Date(product.updatedAt),
lastSynced: new Date()
}
},
upsert: true
}
}))
await Product.bulkWrite(operations)
}
async updateSyncStatus(status) {
await Shop.updateOne(
{ domain: this.shop },
{ $set: { syncStatus: status } }
)
}
async updateLastSync() {
await Shop.updateOne(
{ domain: this.shop },
{ $set: { lastSync: new Date() } }
)
}
async reportProgress(synced) {
// 发送进度更新到前端
global.io?.emit(`sync-progress-${this.shop}`, { synced })
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
module.exports = SyncService
性能优化
1. API调用优化
// services/RateLimiter.js
class ShopifyRateLimiter {
constructor() {
this.buckets = new Map()
this.maxPoints = 40 // Shopify REST API限制
this.refillRate = 2 // 每秒恢复点数
}
async consume(shop, points = 1) {
const bucket = this.getBucket(shop)
if (bucket.points < points) {
const waitTime = Math.ceil((points - bucket.points) / this.refillRate * 1000)
await this.sleep(waitTime)
bucket.points += Math.floor(waitTime / 1000 * this.refillRate)
}
bucket.points -= points
bucket.lastUpdate = Date.now()
return true
}
getBucket(shop) {
if (!this.buckets.has(shop)) {
this.buckets.set(shop, {
points: this.maxPoints,
lastUpdate: Date.now()
})
}
const bucket = this.buckets.get(shop)
const now = Date.now()
const elapsed = (now - bucket.lastUpdate) / 1000
// 恢复点数
bucket.points = Math.min(
this.maxPoints,
bucket.points + elapsed * this.refillRate
)
bucket.lastUpdate = now
return bucket
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
// 使用限流器
const rateLimiter = new ShopifyRateLimiter()
async function makeShopifyRequest(shop, url, options) {
await rateLimiter.consume(shop)
const response = await fetch(url, {
...options,
headers: {
'X-Shopify-Access-Token': await getAccessToken(shop),
...options.headers
}
})
// 检查API限制头部
const callsLeft = response.headers.get('x-shopify-shop-api-call-limit')
if (callsLeft) {
const [used, limit] = callsLeft.split('/')
if (parseInt(used) > parseInt(limit) * 0.8) {
console.warn(`API usage high for ${shop}: ${used}/${limit}`)
}
}
return response
}
2. 缓存策略
// services/CacheService.js
const Redis = require('redis')
class CacheService {
constructor() {
this.redis = Redis.createClient(process.env.REDIS_URL)
this.defaultTTL = 300 // 5分钟
}
async get(key) {
try {
const value = await this.redis.get(key)
return value ? JSON.parse(value) : null
} catch (error) {
console.error('Cache get error:', error)
return null
}
}
async set(key, value, ttl = this.defaultTTL) {
try {
await this.redis.setex(key, ttl, JSON.stringify(value))
} catch (error) {
console.error('Cache set error:', error)
}
}
async del(key) {
try {
await this.redis.del(key)
} catch (error) {
console.error('Cache delete error:', error)
}
}
// 带缓存的数据获取
async getOrFetch(key, fetchFn, ttl = this.defaultTTL) {
let data = await this.get(key)
if (data === null) {
data = await fetchFn()
await this.set(key, data, ttl)
}
return data
}
// 智能缓存失效
async invalidatePattern(pattern) {
try {
const keys = await this.redis.keys(pattern)
if (keys.length > 0) {
await this.redis.del(keys)
}
} catch (error) {
console.error('Cache invalidation error:', error)
}
}
}
// 使用缓存服务
const cache = new CacheService()
async function getShopProducts(shop) {
const cacheKey = `products:${shop}`
return await cache.getOrFetch(cacheKey, async () => {
const response = await makeShopifyRequest(
shop,
`https://${shop}.myshopify.com/admin/api/2023-10/products.json`
)
return await response.json()
}, 600) // 10分钟缓存
}
错误处理和监控
1. 全局错误处理
// middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
console.error('Error:', err)
// Shopify API错误
if (err.name === 'ShopifyAPIError') {
return res.status(err.statusCode || 500).json({
error: 'Shopify API Error',
message: err.message,
shop: req.query.shop
})
}
// 认证错误
if (err.name === 'AuthenticationError') {
return res.status(401).json({
error: 'Authentication failed',
message: 'Please reinstall the app'
})
}
// 权限错误
if (err.name === 'PermissionError') {
return res.status(403).json({
error: 'Insufficient permissions',
message: err.message
})
}
// 限流错误
if (err.name === 'RateLimitError') {
return res.status(429).json({
error: 'Rate limit exceeded',
retryAfter: err.retryAfter
})
}
// 默认错误
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong'
})
}
module.exports = errorHandler
2. 监控和日志
// services/MonitoringService.js
class MonitoringService {
constructor() {
this.metrics = {
apiCalls: 0,
errors: 0,
averageResponseTime: 0
}
}
// 记录API调用
recordAPICall(shop, endpoint, responseTime, success = true) {
this.metrics.apiCalls++
if (!success) {
this.metrics.errors++
}
// 更新平均响应时间
this.metrics.averageResponseTime =
(this.metrics.averageResponseTime + responseTime) / 2
// 发送到监控服务
this.sendMetric('api_call', {
shop,
endpoint,
responseTime,
success,
timestamp: new Date()
})
}
// 记录错误
recordError(error, context = {}) {
const errorData = {
message: error.message,
stack: error.stack,
context,
timestamp: new Date(),
level: 'error'
}
console.error('Application Error:', errorData)
// 发送到错误追踪服务
this.sendToErrorTracking(errorData)
}
// 健康检查
async healthCheck() {
const checks = {
database: await this.checkDatabase(),
redis: await this.checkRedis(),
shopifyAPI: await this.checkShopifyAPI()
}
const isHealthy = Object.values(checks).every(check => check.status === 'ok')
return {
status: isHealthy ? 'healthy' : 'unhealthy',
checks,
metrics: this.metrics,
timestamp: new Date()
}
}
async checkDatabase() {
try {
await mongoose.connection.db.admin().ping()
return { status: 'ok', message: 'Database connected' }
} catch (error) {
return { status: 'error', message: error.message }
}
}
async checkRedis() {
try {
await cache.redis.ping()
return { status: 'ok', message: 'Redis connected' }
} catch (error) {
return { status: 'error', message: error.message }
}
}
async checkShopifyAPI() {
try {
// 测试API连接
return { status: 'ok', message: 'Shopify API accessible' }
} catch (error) {
return { status: 'error', message: error.message }
}
}
sendMetric(name, data) {
// 发送到监控服务 (如DataDog, New Relic等)
}
sendToErrorTracking(errorData) {
// 发送到错误追踪服务 (如Sentry)
}
}
module.exports = new MonitoringService()
部署和运维
1. Docker部署
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
RUN npm ci --only=production
# 复制应用代码
COPY . .
# 构建前端资源
RUN npm run build
# 创建非root用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
- SHOPIFY_API_KEY=${SHOPIFY_API_KEY}
- SHOPIFY_API_SECRET=${SHOPIFY_API_SECRET}
depends_on:
- db
- redis
restart: unless-stopped
db:
image: mongo:5
volumes:
- mongodb_data:/data/db
environment:
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USERNAME}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
mongodb_data:
redis_data:
2. 环境配置
// config/environment.js
const config = {
development: {
port: 3000,
host: 'localhost',
shopifyApiUrl: 'https://your-app.ngrok.io',
database: {
url: 'mongodb://localhost:27017/shopify-app-dev'
},
redis: {
url: 'redis://localhost:6379'
},
logging: {
level: 'debug'
}
},
production: {
port: process.env.PORT || 3000,
host: '0.0.0.0',
shopifyApiUrl: process.env.SHOPIFY_APP_URL,
database: {
url: process.env.DATABASE_URL
},
redis: {
url: process.env.REDIS_URL
},
logging: {
level: 'info'
}
}
}
module.exports = config[process.env.NODE_ENV || 'development']
最佳实践总结
安全原则
- 验证所有输入:始终验证来自Shopify的数据
- 安全存储凭据:使用环境变量存储敏感信息
- HTTPS通信:确保所有通信都使用HTTPS
- 定期更新依赖:保持依赖包的更新
性能原则
- 实施缓存:缓存频繁访问的数据
- 限制API调用:遵守Shopify的API限制
- 异步处理:使用队列处理耗时操作
- 监控性能:持续监控应用性能
可维护性原则
- 模块化设计:保持代码结构清晰
- 错误处理:实施完善的错误处理机制
- 文档完整:维护完整的API文档
- 测试覆盖:编写全面的测试用例
总结
Shopify应用开发是一个复杂但回报丰厚的过程。通过掌握认证、API操作、扩展开发和部署等关键技术,您可以创建出功能强大、性能优异的Shopify应用。
记住,成功的应用不仅要有强大的功能,还要有良好的用户体验、稳定的性能和完善的错误处理机制。持续学习和改进是应用开发的关键。
最后更新时间: