从0到1构建全栈订阅电商:Crate开源项目实战指南

从0到1构建全栈订阅电商:Crate开源项目实战指南

【免费下载链接】crate 👕 👖 📦 A sample web and mobile application built with Node, Express, React, React Native, Redux and GraphQL. Very basic replica of stitchfix.com / krate.in (allows users to get monthly subscription of trendy clothes and accessories). 【免费下载链接】crate 项目地址: https://gitcode.com/gh_mirrors/cra/crate

引言:你还在为全栈项目架构发愁吗?

作为开发者,你是否曾面临这些痛点:

  • 想学习全栈开发却找不到结构清晰的实战项目
  • GraphQL与React/React Native整合困难重重
  • 多端应用(Web+Mobile)状态管理混乱
  • 开源项目要么过于简单要么庞大难懂

本文将带你深度剖析Crate项目——一个基于Node.js、Express、React、React Native和GraphQL构建的订阅制电商应用,完整复刻StitchFix商业模式。通过本文,你将掌握:

  • 现代化全栈项目的架构设计与模块划分
  • GraphQL API的设计与实现技巧
  • React与React Native代码复用策略
  • Redux状态管理在多端应用中的最佳实践
  • 从0到1搭建可扩展的订阅服务系统

项目概述:什么是Crate?

Crate是一个开源的全栈应用示例,旨在提供每月时尚服装和配饰订阅服务,类似于StitchFix或Krate.in。该项目采用现代化JavaScript技术栈,构建了完整的Web和移动应用生态系统。

核心功能

  • 用户注册与认证系统
  • 产品浏览与详情展示
  • 月度订阅盒(Crate)管理
  • 个性化推荐系统
  • 订单与订阅管理

技术栈总览

应用层技术框架核心功能
后端 APINode.js, Express, GraphQL提供数据接口与业务逻辑
Web 应用React, Redux, React Router面向用户的Web界面
移动应用React Native, ReduxiOS/Android跨平台应用
数据库MySQL (Sequelize ORM)数据持久化存储
状态管理Redux跨组件状态共享
API通信GraphQL高效数据查询与变更

项目架构

mermaid

环境搭建:从零开始配置开发环境

前置条件

  • Node.js (v14+)
  • MySQL数据库
  • Git
  • React Native开发环境(可选,用于移动应用开发)
  • Xcode/Android Studio(可选,用于移动应用调试)

快速开始步骤

# 1. 克隆仓库
git clone https://gitcode.com/gh_mirrors/cra/crate.git
cd crate/code

# 2. API服务设置
cd api
npm run setup  # 安装依赖并执行数据库迁移和种子文件

# 3. Web应用设置
cd ../web
npm install

# 4. 移动应用设置
cd ../mobile
npm install
cd ios && pod install  # iOS依赖

配置文件详解

API数据库配置 (api/src/config/database.json):

{
  "development": {
    "username": "root",
    "password": "your_password",
    "database": "crate_dev",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

移动应用API配置 (mobile/src/setup/config.json):

{
  "url": {
    "api": "http://你的本地IP:8000"  // 使用ifconfig获取本地IP
  }
}

开发环境启动

# 启动API服务 (默认端口8000)
cd api && npm start

# 启动Web应用 (默认端口3000)
cd web && npm start

# 启动移动应用
cd mobile
npx react-native run-ios  # iOS
# 或
npx react-native run-android  # Android

后端开发:GraphQL API构建详解

API架构设计

Crate后端采用模块化架构,每个业务实体(User, Product, Crate, Subscription)都有独立的模块,包含以下文件结构:

modules/
  ├── user/
  │   ├── model.js      # 数据模型
  │   ├── types.js      # GraphQL类型定义
  │   ├── query.js      # 查询解析器
  │   ├── mutations.js  # 变更解析器
  │   └── resolvers.js  # 业务逻辑实现
  ├── product/
  ├── crate/
  └── subscription/

GraphQL服务配置

// api/src/setup/graphql.js
import graphqlHTTP from 'express-graphql'
import serverConfig from '../config/server.json'
import authentication from './authentication'
import schema from './schema'

export default function (server) {
  console.info('SETUP - GraphQL...')

  server.use(authentication)

  server.use(serverConfig.graphql.endpoint, graphqlHTTP(request => ({
    schema,
    graphiql: serverConfig.graphql.ide,
    pretty: serverConfig.graphql.pretty,
    context: {
      auth: {
        user: request.user,
        isAuthenticated: request.user && request.user.id > 0
      }
    }
  })))
}

数据模型定义

以Crate模块为例,数据模型定义如下:

// api/src/modules/crate/types.js
import { GraphQLObjectType, GraphQLString, GraphQLInt } from 'graphql'

const CrateType = new GraphQLObjectType({
  name: 'crate',
  description: 'Crate Type',

  fields: () => ({
    id: { type: GraphQLInt },
    name: { type: GraphQLString },
    description: { type: GraphQLString },
    createdAt: { type: GraphQLString },
    updatedAt: { type: GraphQLString }
  })
})

export default CrateType

典型查询示例

获取所有订阅盒

query {
  crates {
    id
    name
    description
  }
}

创建订阅

mutation {
  subscriptionCreate(userId: 1, crateId: 2) {
    id
    status
    nextDeliveryDate
  }
}

前端开发:React Web应用实现

项目结构

Web应用采用基于功能模块的组织结构:

src/
├── modules/           # 业务模块
│   ├── auth/          # 认证相关
│   ├── product/       # 产品相关
│   ├── crate/         # 订阅盒相关
│   ├── subscription/  # 订阅相关
│   └── user/          # 用户相关
├── setup/             # 应用设置
└── ui/                # UI组件库

产品详情组件示例

// web/src/modules/product/Detail.js
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { Helmet } from 'react-helmet'

// UI组件
import { Grid, GridCell } from '../../ui/grid'
import Card from '../../ui/card/Card'
import { H2, H3, H4 } from '../../ui/typography'
import { grey, grey2 } from '../../ui/common/colors'

// 应用导入
import { routeImage, routes } from '../../setup/routes'
import { renderIf } from '../../setup/helpers'
import { get } from './api/actions'
import Loading from '../common/Loading'
import Related from './Related'

class Detail extends PureComponent {
  static fetchData({ store, params }) {
    return store.dispatch(get(params.slug))
  }

  componentDidMount() {
    this.refresh(this.props.match.params.slug)
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.match.params.slug !== this.props.match.params.slug) {
      this.refresh(nextProps.match.params.slug)
    }
  }

  refresh = (slug) => {
    this.props.get(slug)
  }

  render() {
    const { isLoading, item, error } = this.props.product

    return (
      <div>
        {
          !error
            ? isLoading
              ? <Loading/>
              : renderIf(item && item.id, () => (
                  <div>
                    {/* SEO设置 */}
                    <Helmet>
                      <title>{`Product - ${ item.name }`}</title>
                      <meta name="description" content={`Product - ${ item.name }`} />
                      <meta property="og:image" content={routeImage + item.image} />
                    </Helmet>

                    {/* 产品详情 */}
                    <Grid style={{ backgroundColor: grey }}>
                      <GridCell style={{ padding: '2em', textAlign: 'center' }}>
                        <H3 font="secondary">Product</H3>
                      </GridCell>
                    </Grid>

                    <Grid gutter={true} alignCenter={true} style={{ padding: '2em' }}>
                      {/* 产品图片 */}
                      <GridCell style={{ maxWidth: '35em' }}>
                        <Card>
                          <img src={routeImage + item.image} alt={item.name} style={{ width: '100%' }}/>
                        </Card>
                      </GridCell>

                      {/* 产品信息 */}
                      <GridCell style={{ textAlign: 'center' }}>
                        <H2 font="secondary">{item.name}</H2>
                        <H4 style={{ marginTop: '1em' }}>{item.description}</H4>
                        <p style={{ marginTop: '0.5em', color: grey2 }}>
                          Launched on { new Date(parseInt(item.createdAt)).toDateString() }
                        </p>
                      </GridCell>
                    </Grid>

                    {/* 相关产品 */}
                    <Related productId={item.id}/>
                  </div>
                ))
            : <Redirect to={routes.home.path}/>
        }
      </div>
    )
  }
}

// 连接Redux状态
function detailState(state) {
  return {
    product: state.product
  }
}

export default withRouter(connect(detailState, { get })(Detail))

移动开发:React Native应用实现

订阅盒列表组件

// mobile/src/modules/crate/List/index.js
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { View, ScrollView } from 'react-native'
import { withNavigation } from 'react-navigation'

// UI组件
import styles from './styles'

// 应用导入
import { getList as getCratesList } from '../../crate/api/actions'
import { getListByUser as getSubscriptionListByUser } from '../../subscription/api/actions'
import Loading from '../../common/Loading'
import EmptyMessage from '../../common/EmptyMessage'
import CrateItem from '../../crate/Item'
import { routes } from '../../../setup/routes'

class List extends PureComponent {
  componentDidMount() {
    const { dispatch } = this.props
    dispatch(getCratesList())
  }

  #onSuccessSubscription = () => {
    const { navigation, dispatch } = this.props
    dispatch(getSubscriptionListByUser(this.props.user.details.email))
    navigation.navigate(routes.account.name)
  }

  render() {
    const { isLoading, list } = this.props.crates

    return (
      <View style={styles.container}>
        {
          isLoading
            ? <Loading />
            : list && list.length > 0
              ? <ScrollView style={styles.itemContainer}>
                  { list.map((crate, i) => (
                    <CrateItem
                      key={crate.id}
                      crate={crate}
                      lastItem={list.length - 1 === i}
                      onSuccessSubscription={this.#onSuccessSubscription}
                    />
                  )) }
                </ScrollView>
              : <EmptyMessage message={'No crate is available at the moment'} />
        }
      </View>
    )
  }
}

// 连接Redux状态
function listState(state) {
  return {
    crates: state.crates,
    user: state.user
  }
}

export default connect(listState)(withNavigation(List))

移动应用配置

移动应用通过配置文件连接到API服务:

{
  "url": {
    "api": "http://192.168.225.124:8000"
  },
  "product": {
    "types": {
      "cloth": { "id": 1, "name": "Cloth" },
      "accessory": { "id": 2, "name": "Accessories" }
    }
  },
  "user": {
    "gender": {
      "male": { "id": 1, "name": "Male" },
      "female": { "id": 2, "name": "Female" }
    }
  }
}

高级功能:订阅系统实现

订阅流程

mermaid

订阅管理API

订阅模块提供完整的生命周期管理:

// API解析器示例 (subscription/resolvers.js)
export const getAll = async () => {
  return await Subscription.findAll()
}

export const getByUser = async (userEmail) => {
  return await Subscription.findAll({
    where: { userEmail },
    include: [Crate]
  })
}

export const create = async (userId, crateId, status = 'active') => {
  // 计算下次配送日期
  const nextDeliveryDate = new Date()
  nextDeliveryDate.setMonth(nextDeliveryDate.getMonth() + 1)

  return await Subscription.create({
    userId,
    crateId,
    status,
    nextDeliveryDate: nextDeliveryDate.toISOString()
  })
}

export const cancel = async (id) => {
  const subscription = await Subscription.findByPk(id)
  
  if (!subscription) {
    throw new Error('Subscription not found')
  }
  
  subscription.status = 'cancelled'
  return await subscription.save()
}

部署与扩展

生产环境构建

# API服务构建
cd api && npm run build

# Web应用构建
cd web && npm run build

# 移动应用构建
# iOS
cd mobile && react-native run-ios --configuration Release
# Android
cd mobile && react-native run-android --variant=release

部署架构

mermaid

总结与展望

项目亮点回顾

  1. 技术栈整合:成功整合Node.js、Express、React、React Native、Redux和GraphQL构建全栈应用
  2. 模块化架构:前后端均采用模块化设计,提高代码复用和维护性
  3. 代码共享:业务逻辑和数据模型在Web和移动应用间共享
  4. 性能优化:使用PureComponent、SSR等技术提升应用性能
  5. 可扩展性:架构设计支持未来功能扩展和业务增长

可能的扩展方向

  1. 支付集成:添加Stripe或PayPal支付处理
  2. 高级推荐:实现基于用户偏好的个性化推荐系统
  3. 实时通知:集成WebSocket实现实时订单状态更新
  4. 分析功能:添加用户行为分析和业务数据报表
  5. 多语言支持:实现国际化(i18n)支持

学习资源

  • 项目GitHub仓库:https://gitcode.com/gh_mirrors/cra/crate
  • GraphQL文档:https://graphql.org/learn/
  • React Native文档:https://reactnative.dev/docs/getting-started
  • Sequelize ORM:https://sequelize.org/master/

结语

Crate项目展示了如何使用现代JavaScript技术栈构建复杂的全栈应用。通过本文的指南,你应该能够理解项目架构、核心功能实现以及如何在本地环境中运行和扩展该项目。

无论是作为学习全栈开发的示例,还是作为实际项目的起点,Crate都提供了丰富的代码和最佳实践参考。希望这个开源项目能够帮助你加速开发旅程!

如果你觉得本指南有帮助,请点赞、收藏并关注项目仓库以获取最新更新。下一篇教程将深入探讨如何为Crate添加支付系统和高级推荐功能,敬请期待!

贡献指南

Crate是一个开源项目,欢迎社区贡献:

  1. Fork本仓库
  2. 创建特性分支 (git checkout -b feature/amazing-feature)
  3. 提交更改 (git commit -m 'Add some amazing feature')
  4. 推送到分支 (git push origin feature/amazing-feature)
  5. 创建Pull Request

【免费下载链接】crate 👕 👖 📦 A sample web and mobile application built with Node, Express, React, React Native, Redux and GraphQL. Very basic replica of stitchfix.com / krate.in (allows users to get monthly subscription of trendy clothes and accessories). 【免费下载链接】crate 项目地址: https://gitcode.com/gh_mirrors/cra/crate

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值