Создание приложения электронной коммерции GraphQL с нуля
6 декабря 2022 г.
Это то, что вы будете строить!
A Опрос, проведенный Accenture (n>20 000 потребителей в 19 странах), показал, что 47 % покупателей в Интернете готовы платить больше, если им будет предоставлен опыт электронной коммерции, который превзойдет их ожидания.
Отличные новости, если вы интернет-магазин, верно? Но предостерегайте покупателей: точно такой же процент также сказал, что вместо этого они избегали бы покупать у розничного продавца, если бы их опыт разочаровывал.
Быстрый, отзывчивый и интуитивно понятный интерфейс для ваших покупателей имеет решающее значение, поэтому JAMstack — JavaScript, API, разметка — оказался популярным для электронной коммерции. Однако это еще не все, что вам нужно.
Суть здесь в том, чтобы отделить интерфейсную часть от серверной, сократить разрыв с помощью GraphQL и умного управления отрисовкой контента... и именно поэтому WunderGraph a> — инструмент, объединяющий REST, GraphQL и все ваши источники данных в единый, безопасный, типобезопасный, сквозной путь для всех ваших пользовательских интерфейсов — имеет смысл.
Надежная технология с современным дизайном.
Итак, давайте посмотрим, как мы можем использовать некоторые из этих технологий JAMstack — Next.js , Strapi , GraphQL и Snipcart a> – вместе таким образом, чтобы вы могли создать именно тот опыт покупок для своих пользователей, который вам нужен, и при этом не идти на компромиссы с разработчиками.
Вид с высоты 30 000 футов
Вот что вам понадобится для этого урока:
- Node.js & Установлены NPM и Python (требуется для локального тестирования Strapi с
sqlite3
). - Промежуточные знания React/Next.js, TypeScript и CSS (здесь я использую Tailwind, потому что мне нравится CSS, ориентированный на полезность ; но классы Tailwind довольно хорошо переводятся практически в любое другое решение для стилей, потому что это буквально просто другой способ написания обычного CSS).
- Автономная CMS для быстрой загрузки нашего бэкэнда (и добавления GraphQL) без необходимости иметь дело с громоздкими сторонними клиентами, которые нам придется поставлять на стороне клиента. Strapi великолепен, поэтому мы используем его здесь.
- Snipcart учетная запись для добавления системы управления корзиной в наше приложение электронной коммерции (бесплатно в тестовом режиме). В этом руководстве используется Snipcart V3.
Во-вторых, вот краткая схема нашей архитектуры, чтобы вы знали, что будете создавать.
Бэкенд — Strapi + GraphQL
Хотя реляционная база данных сама по себе подойдет для большинства случаев использования, готовая к работе электронная коммерция — это то, где вам нужно нечто большее.
Таким образом, мы будем использовать безголовую JAMstack CMS, Strapi, в качестве PIM — решения для управления продуктом, информацией, управлением — единым источником. истины для вашей платформы электронной коммерции. Это будет центр для вашего каталога продуктов, откуда вы будете добавлять, изменять, обогащать и распространять свой каталог продуктов (как GraphQL через легко устанавливаемый плагин).
В этом руководстве в качестве базы данных Strapi используется sqlite3
, но вы можете легко заменить ее в рабочей среде на PostgreSQL или другую по вашему выбору.
Лучший друг — WunderGraph
WunderGraph — ключ ко всему этому. Он находится между нашим интерфейсом и серверной частью, одновременно служа связующим звеном для их разделения и облегчения безопасного взаимодействия с типами.
Это BFF — Backend-for-Frontend — сервисный уровень. , или API-шлюз, как бы вы его ни называли, который служит единственным «бэкэндом», который может видеть ваш интерфейс.
Это работает путем консолидации данных из всех ваших источников данных (для нас здесь — конечной точки GraphQL) и использования запросов GraphQL, которые вы пишете, чтобы адаптировать эти данные (через безопасный JSON-RPC) для каждого отдельного взаимодействия с пользователем, которое вы хотите предоставить.
Вы можете использовать фильтры, объединения, переводы и т. д., сохраняя при этом четкое разделение обязанностей между приложением Next.js и серверной частью Strapi + DB и освобождая их для выполнения собственных задач.
Внешний интерфейс — Next.js + Tailwind CSS + Snipcart
Фронтенд на самом деле не слишком сложен. WunderGraph генерирует невероятно полезные готовые к использованию хуки для запроса и изменения данных (опять же, на основе написанных вами операций GraphQL), поэтому для Next.js вы можете просто использовать их.
Snipcart – это отличный способ добавить простую в интеграции и красивую систему управления корзинами в любое веб-приложение, и она сослужит нам хорошую службу.
Эта архитектура позволяет реализовать возможности электронной коммерции в виде молниеносно быстрого, модульного, расширяемого интерфейса, а не громоздких универсальных клиентских приложений, которые были в далеком прошлом.
Вот готовый продукт с разными размерами экрана.
1. Создайте серверную часть Strapi + GraphQL
Strapi – это автономная CMS (система управления контентом) на основе Node.js с открытым исходным кодом, которая позволяет программистам быстро создавать самостоятельные, адаптируемые и эффективные API контента (RESTful и GraphQL) без написания кода вообще. р>
Шаг 1. Создание проекта Strapi
Создайте новый каталог, перейдите в него и выполните следующую команду:
npx create-strapi-app@latest my-project - quickstart
💡 Флаг --quickstart
по умолчанию дает вам базу данных SQLite 3. Если вы предпочитаете переключаться между базами данных, см. здесь .
Теперь ваше приложение Strapi запущено и работает по адресу localhost:1337
.
Зарегистрируйте администратора проекта Strapi. Это полностью локально для разработки и тестирования. Вам не нужно беспокоиться о действительных адресах электронной почты, надежных паролях и т. п.... пока.
Теперь у вас должен быть доступ к панели управления Strapi по адресу localhost:1337/admin
.
Шаг 2. Создайте типы контента для каталога товаров.
Панель администратора Strapi позволяет быстро создать схему для ваших данных без необходимости возиться с реальной реляционной базой данных. Strapi называет эти модели типами контента. Они довольно интуитивно понятны, все, что вам нужно сделать, это создать новый Content-Type и добавить поля, которые нужны вашим продуктам.
Перейдите в раздел Content-Type Builder на боковой панели и выберите Создать новый тип коллекции. Появится модальное окно, где вы должны ввести продукт в качестве отображаемого имени, а затем нажать кнопку «Продолжить».
Следующая часть занята. Далее вам нужно будет добавить фактические данные (Диспетчер контента –> выберите Тип коллекции –> Создать новую запись, промойте и повторите для каждого продукта в вашем каталоге) Я загружаю примеры данных из FakeStoreAPI , поэтому в соответствии с этим мои типы контента выглядят так.
Не забудьте добавить отношение «многие к одному» между товаром и категорией!
Затем вам нужно будет предоставить разрешения на чтение для find
и findOne
для двух типов коллекций, чтобы они были общедоступными и их можно было запрашивать без аутентификации.
Для этого перейдите в «Настройки» на боковой панели, а затем:
- Перейдите к разделу Роли в разделе Пользователи и разрешения .
- Нажмите на роль Общедоступная, чтобы изменить ее.
- Нажмите раскрывающееся меню Разрешения пользователей, затем выберите параметры поиска и поиска для продукта и категории.
Теперь нажмите кнопку Сохранить в правом верхнем углу экрана, чтобы обновить роль.
Теперь мы можем выполнять запросы REST, такие как GET /products
и GET /products/:id
. Но, конечно же, GraphQL сделал бы все это намного намного проще. Итак, давайте сделаем это дальше.
Шаг 3. Добавление GraphQL API
Чтобы преобразовать наши конечные точки API (в настоящее время RESTful) в одну конечную точку GraphQL, нам нужно установить подключаемый модуль graphql, выполнив следующую команду в нашем внутреннем каталоге:
npm run strapi install graphql
Все готово, теперь (пере)запустите сервер Strapi, используя:
npm run develop
...и оставьте его работающим до конца этого урока. Теперь у вас есть конечная точка GraphQL по адресу localhost:1337/graphql
, и вы можете немного поиграть здесь**,** написав запросы GraphQL для изучения ваших данных.
2. Настройте WunderGraph и наш внешний интерфейс.
Теперь, когда у нас есть источник данных GraphQL, пришло время настроить WunderGraph в качестве нашего лучшего друга. К счастью, разработчики WunderGraph предоставили стартовый шаблон, который мы можем использовать для получения как WunderGraph, так и шаблона. Приложение Next.js запущено и запущено одновременно.
Шаг 1. Быстрое начало работы с WunderGraph и Next.js
компакт-диск в корень проекта (и из бэкэнда) и введите:
npx -y @wundergraph/wunderctl init - template nextjs-starter -o frontend
Затем компакт-диск в каталог проекта:
cd frontend
Установите зависимости и запустите:
npm i && npm start
Это загрузит серверы WunderGraph и Next.js (с использованием npm-run- all), предоставляя вам заставку/вступительную страницу по адресу localhost:3000
с примером запроса (я полагаю, для ракет SpaceX). Если вы это видите, все работает.
Теперь, чтобы фактически настроить WunderGraph для просмотра наших данных Strapi и создать для нас клиент Next.js — в комплекте с перехватчиками запросов/мутаций, которые наш интерфейс может затем использовать и отображать данные из нашего каталога продуктов Strapi.
Шаг 2. WunderGraph 101
Таким образом, WunderGraph работает следующим образом: вы сообщаете ему, от каких источников данных зависит ваше приложение, и он объединяет эти разрозненные источники данных в один слой виртуального графа, на котором вы затем можете определять операции с данными (используя GraphQL). Благодаря мощному самоанализу WunderGraph может превратить практически любой источник данных, о котором вы только можете подумать, в безопасный, типобезопасный API JSON-over-RPC; OpenAPI REST, GraphQL, PlanetScale, Fauna, MongoDB и другие, а также любую базу данных Postgres/SQLite/MySQL.
Это отлично работает для нас; у нас уже есть работающий источник данных GraphQL!
Итак, давайте приступим к делу. Откройте wundergraph.config.ts
в каталоге .wundergraph
и добавьте нашу конечную точку Strapi + GraphQL в качестве источника данных, от которого зависит наше приложение и который WunderGraph должен анализировать самостоятельно. р>
// existing code here
const strapi = introspect.graphql({
apiNamespace: 'backend',
url: 'http://localhost:1337/graphql',
})
const myApplication = new Application({
name: 'app',
apis: [strapi],
})
// more existing code, leave all of this alone
Обратите внимание, что для каждого интроспективного источника данных требуется пространство имен, поскольку каждый из них должен быть объединен в единый виртуальный граф.
После того, как вы запустите npm start
, WunderGraph автоматически отслеживает необходимые файлы в каталоге вашего проекта, поэтому просто нажав здесь, вы запустите генератор кода, и он сгенерирует схему, которую вы можете проверить (если вы хотите) — файл wundergraph.app.schema.graphql
в /.wundergraph/generated
.
Шаг 3. Определение операций с помощью GraphQL
Это та часть, где мы пишем запросы/мутации в GraphQL для работы с созданным WunderGraph слоем виртуального графа и получаем нужные нам данные.
Итак, перейдите в ./wundergraph/operations
и создайте новый файл GraphQL. Мы назовем его AllProducts.graphql
. Имя файла/запроса не имеет значения; его содержимое и пространство имен (в формате 'namespace_collection
') подходят.
Итак, это наш запрос, чтобы получить все продукты.
query AllProducts {
backend_products {
data {
id
attributes {
title
price
image
description
review_score
review_count
}
}
}
}
Пока не празднуйте; вот еще одна операция, на этот раз для получения одного продукта по идентификатору (или slug. Ваш выбор, но slug, вероятно, будет лучше для SEO).
query ProductByID($id: ID!) {
backend_products(filters: { id: { eq: $id } }) {
data {
id
attributes {
title
image
price
description
review_score
review_count
category {
data {
id
attributes {
name
}
}
}
}
}
}
}
Каждый раз, когда вы нажимали «Сохранить» в течение всего этого процесса, генерация кода WunderGraph работала в фоновом режиме (и будет работать, пока работает его сервер), генерируя типобезопасные, специфичные для клиента данные, извлекающие хуки React «на лету» для вы.
Вы можете просмотреть их в /components/nextjs.ts
.
// existing code here
export const useQuery = {
AllProducts: (args: QueryArgsWithInput<AllProductsInput>) =>
hooks.useQueryWithInput<AllProductsInput, AllProductsResponseData, Role>(
WunderGraphContext,
{
operationName: 'AllProducts',
requiresAuthentication: false,
}
)(args),
ProductByID: (args: QueryArgsWithInput<ProductByIDInput>) =>
hooks.useQueryWithInput<ProductByIDInput, ProductByIDResponseData, Role>(
WunderGraphContext,
{
operationName: 'ProductByID',
requiresAuthentication: false,
}
)(args),
}
// more existing code. Change nothing.
Отлично; сейчас вы, наверное, можете немного отпраздновать.
Как вы можете заметить, WunderGraph предоставил вам два крючка, которые вы можете использовать при создании своего сайта — AllProducts
и ProductByID
. Оба принимают входные данные; первое, ограничение на разбивку на страницы, а второе, ну... идентификатор для фильтрации, конечно.
<сильный>3. Создайте внешний интерфейс Next.js
Хорошие новости; большая часть нашей тяжелой работы сделана. С этого момента мы полностью живем в мире пользовательского интерфейса. Если вы знакомы с Next.js, это не составит труда. Все, что мы будем делать, — это создавать внешний интерфейс для нашего приложения электронной коммерции, используя те 2 хука, которые были экспортированы на предыдущем шаге.
import Head from 'next/head'
import '../styles/global.css'
function MyApp({ Component, pageProps }) {
return (
<>
<Head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com"></script>
</Head>
<main className="min-h-screen justify-center dark:bg-neutral-900">
<Component {...pageProps} />
</main>
</>
)
}
export default MyApp
Ваш файл _app.tsx
import { NextPage } from 'next'
/* WG generated hooks */
import { useQuery, withWunderGraph } from '../components/generated/nextjs'
/* my components */
import NavBar from '../components/NavBar'
import ProductCard from '../components/ProductCard'
import Snipcart from '../components/Snipcart'
/* types */
import { result } from '../types/AllProductsResult'
const Home: NextPage = () => {
const allProducts = useQuery.AllProducts({
input: { a: { pageSize: 10 } },
}).result as result
return (
<div>
<NavBar />
<Snipcart />
<div className="relative mx-auto flex flex-col items-center">
<div className="grid gap-4 p-4 md:grid-cols-2 lg:grid-cols-3">
{allProducts.data ? (
<>
{allProducts.data.backend_products.data.map((product) => {
return (
<ProductCard
id={product.id}
title={product.attributes.title}
image={product.attributes.image}
description={product.attributes.description}
price={product.attributes.price}
rating={product.attributes.review_score}
reviews={product.attributes.review_count}
/>
)
})}
</>
) : (
<>
<span> Loading...</span>
</>
)}
</div>
</div>
</div>
)
}
export default withWunderGraph(Home)
Ваш файл index.tsx
/* NextJS stuff */
import Image from 'next/image'
import { useRouter } from 'next/router'
/* my styles */
import styles from './ProductCard.module.css'
type Props = {
id: string
title: string
image: string
price: number
description?: string
rating: number
reviews: number
}
const ProductCard = (props: Props) => {
const router = useRouter()
// handle click on Product Image
const routeTo = (productId) => {
router.push({
pathname: `/products/${productId}`,
})
}
return (
<div className={styles.container}>
<div className={styles.card}>
<div className={styles.imgBx}>
<Image
onClick={() => routeTo(props.id)}
className="img"
src={props.image}
alt="Product image"
height={300}
width={300}
/>
</div>
<div className={styles.contentBx}>
<div className={styles.title}>
<h2 onClick={() => routeTo(props.id)}>{props.title}</h2>
</div>
<div className={styles.rating}>
<h2 className="font-medium text-zinc-100">{props.rating}⭐ </h2>
<h4 className="font-medium text-amber-500">{props.reviews} </h4>
</div>
<div className={styles.price}>
<h4 className="text-2xl font-bold text-gray-800">${props.price}</h4>
</div>
<div className={styles.cartBtn}>
{/* <a href="#">Add 🛒</a> */}
<button
className="snipcart-add-item"
data-item-id={props.id}
data-item-price={'' + props.price}
data-item-description={props.description}
data-item-image={props.image}
data-item-name={props.title}
>
Add 🛒
</button>
</div>
</div>
</div>
</div>
)
}
export default ProductCard
Ваш файл ProductCard.tsx
/* WG generated hooks */
import { useQuery, withWunderGraph } from '../../components/generated/nextjs'
/* NextJS stuff */
import Image from 'next/image'
import { useRouter } from 'next/router'
/* my components */
import NavBar from '../../components/NavBar'
/* types */
import { result } from '../../types/OneProductResult'
type Props = {}
const Product = (props: Props) => {
const router = useRouter()
const { id } = router.query
const productId: string = id as string // needed because id as is will be of type string[] | string (union)
const oneProduct = useQuery.ProductByID({
input: { id: productId },
}).result as result
const data = oneProduct.data?.backend_products?.data[0].attributes
return (
<div>
<NavBar />
<div>
<div className="items-center">
<div className="grid grid-cols-2 gap-0 p-4">
<div className="content-center">
<Image
src={data?.image}
alt="Picture of product"
width={400}
height={400}
/>
</div>
<div>
<h1 className="text-4xl font-bold text-zinc-100">
{data?.title}
</h1>
<h2 className="mt-2 mb-2 bg-amber-500 p-2 text-2xl font-bold text-black ">
${data?.price}
</h2>
<h2 className="mt-2 mb-2 text-lg text-neutral-400 ">
{data?.description}
</h2>
<h3 className="mt-2 mb-2 text-xl text-zinc-100 ">
{data?.review_score} ⭐
</h3>
<h3 className="mt-2 mb-2 text-lg text-zinc-100 ">
{data?.review_count} reviews
</h3>
<button
className="snipcart-add-item mt-2 mb-2 bg-zinc-100 p-3 text-xl font-bold text-black"
data-item-id={oneProduct.data?.backend_products?.data[0].id}
data-item-price={'' + data?.price}
data-item-description={data?.description}
data-item-image={data?.image}
data-item-name={data?.title}
>
Add To Cart 🛒
</button>
</div>
</div>
</div>
</div>
</div>
)
}
export default withWunderGraph(Product)
Ваш файл [id].tsx[
4. Реализуйте управление корзиной с помощью Snipcart
Snipcart предоставляет комплексное решение для управления корзиной и оформления заказа для любого веб-приложения, поставляемое в виде пользовательских скриптов и таблиц стилей, что упрощает интеграцию.
После того, как вы зарегистрируетесь, перепроверите, что вы находитесь в тестовом режиме и что у вас есть открытый ключ API (и сохраненный в вашем файле .env.local
), вы все установить.
Только одно изменение: вместо добавления скриптов Snipcart (получите их здесь) вручную в <body> нашего приложения ;
, мы создадим для них компонент и вместо этого включим его в наш Index.tsx
.
/* NextJS stuff */
import Script from 'next/script'
const Snipcart = () => {
return (
<>
<Script
id="show-cart"
dangerouslySetInnerHTML={{
__html: `
window.SnipcartSettings = {
publicApiKey: "${process.env.NEXT_PUBLIC_SNIPCART_API_KEY}",
loadStrategy: "on-user-interaction",
modalStyle: "side",
};
(function(){var c,d;(d=(c=window.SnipcartSettings).version)!=null||(c.version="3.0");var s,S;(S=(s=window.SnipcartSettings).currency)!=null||(s.currency="usd");var l,p;(p=(l=window.SnipcartSettings).timeoutDuration)!=null||(l.timeoutDuration=2750);var w,u;(u=(w=window.SnipcartSettings).domain)!=null||(w.domain="cdn.snipcart.com");var m,g;(g=(m=window.SnipcartSettings).protocol)!=null||(m.protocol="https");var f,v;(v=(f=window.SnipcartSettings).loadCSS)!=null||(f.loadCSS=!0);var E=window.SnipcartSettings.version.includes("v3.0.0-ci")||window.SnipcartSettings.version!="3.0"&&window.SnipcartSettings.version.localeCompare("3.4.0",void 0,{numeric:!0,sensitivity:"base"})===-1,y=["focus","mouseover","touchmove","scroll","keydown"];window.LoadSnipcart=o;document.readyState==="loading"?document.addEventListener("DOMContentLoaded",r):r();function r(){window.SnipcartSettings.loadStrategy?window.SnipcartSettings.loadStrategy==="on-user-interaction"&&(y.forEach(function(t){return document.addEventListener(t,o)}),setTimeout(o,window.SnipcartSettings.timeoutDuration)):o()}var a=!1;function o(){if(a)return;a=!0;let t=document.getElementsByTagName("head")[0],n=document.querySelector("#snipcart"),i=document.querySelector('src[src^="'.concat(window.SnipcartSettings.protocol,"://").concat(window.SnipcartSettings.domain,'"][src$="snipcart.js"]')),e=document.querySelector('link[href^="'.concat(window.SnipcartSettings.protocol,"://").concat(window.SnipcartSettings.domain,'"][href$="snipcart.css"]'));n||(n=document.createElement("div"),n.id="snipcart",n.setAttribute("hidden","true"),document.body.appendChild(n)),$(n),i||(i=document.createElement("script"),i.src="".concat(window.SnipcartSettings.protocol,"://").concat(window.SnipcartSettings.domain,"/themes/v").concat(window.SnipcartSettings.version,"/default/snipcart.js"),i.async=!0,t.appendChild(i)),!e&&window.SnipcartSettings.loadCSS&&(e=document.createElement("link"),e.rel="stylesheet",e.type="text/css",e.href="".concat(window.SnipcartSettings.protocol,"://").concat(window.SnipcartSettings.domain,"/themes/v").concat(window.SnipcartSettings.version,"/default/snipcart.css"),t.prepend(e)),y.forEach(function(h){return document.removeEventListener(h,o)})}function $(t){!E||(t.dataset.apiKey=window.SnipcartSettings.publicApiKey,window.SnipcartSettings.addProductBehavior&&(t.dataset.configAddProductBehavior=window.SnipcartSettings.addProductBehavior),window.SnipcartSettings.modalStyle&&(t.dataset.configModalStyle=window.SnipcartSettings.modalStyle),window.SnipcartSettings.currency&&(t.dataset.currency=window.SnipcartSettings.currency),window.SnipcartSettings.templatesUrl&&(t.dataset.templatesUrl=window.SnipcartSettings.templatesUrl))}})();
`,
}}
/>
</>
)
}
export default Snipcart
Как только это будет сделано, все, что нам нужно сделать, это добавить необходимые реквизиты Snipcart для предоставления метаданных корзины везде, где у нас есть <button>
для добавления элемента в корзину.
// Add to Cart button in ProductCard.tsx
<button
className="snipcart-add-item"
data-item-id={props.id}
data-item-price={'' + props.price}
data-item-description={props.description}
data-item-image={props.image}
data-item-name={props.title}
>
Add 🛒
</button>
Все сделано! Просто убедитесь, что вы прочитали их документы , так как эта статья не является исчерпывающим руководством по работе с Snipcart. API.
Подводя итоги
И это все, ребята! Надеемся, что это руководство дало вам представление о том, как вы можете использовать возможности JAMstack с WunderGraph, чтобы выйти за рамки того, что вы могли бы сделать с помощью одного только инструментария JAMstack.
Значимое разделение интерфейса и сервера, а затем преодоление разрыва между ними и безопасный способ загрузки — вероятно, самая распространенная проблема, когда дело доходит до создания веб-приложений. Новая парадигма, такая как JAMstack, делает работу в Интернете намного проще и быстрее как для разработчиков, так и для пользователей, но имеет свой собственный набор подводных камней и утомительное написание связующего кода.
Используемый с JAMstack и GraphQL в качестве сервисного уровня/BFF, WunderGraph устраняет большинство этих болевых точек, гарантируя, что вы будете заниматься только бизнес-логикой, а также создавать и поставлять потрясающие вещи своим клиентам. Полное комплексное решение без каких-либо дополнительных зависимостей.
Об авторе
Притвиш Нэт — полнофункциональный веб-разработчик, слишком приверженный философии "всегда строить". Когда он не работает над приключенческими игровыми движками на основе парсеров, он, вероятно, качается в The Tragically Hip (снова) или на кухне, совершенствуя свое aglio e olio. Проверьте его на https://medium.com/@prithwish.nath.
Также опубликовано здесь
Оригинал