Files
medusa-store/www/apps/docs/content/recipes/marketplace.mdx
Shahed Nasser bb87db8342 docs: prep for v2 documentation (#6710)
This PR includes documentation that preps for v2 docs (but doesn't introduce new docs).

_Note: The number of file changes in the PR is due to find-and-replace within the `references` which is unavoidable. Let me know if I should move it to another PR._

## Changes

- Change Medusa version in base OAS used for v2.
- Fix to docblock generator related to not catching all path parameters.
- Added typedoc plugin that generates ER Diagrams, which will be used specifically for data model references in commerce modules.
- Changed OAS tool to output references in `www/apps/api-reference/specs-v2` directory when the `--v2` option is used.
- Added a version switcher to the API reference to switch between V1 and V2. This switcher is enabled by an environment variable, so it won't be visible/usable at the moment.
- Upgraded docusaurus to v3.0.1
- Added new Vale rules to ensure correct spelling of Medusa Admin and module names.
- Added new components to the `docs-ui` package that will be used in future documentation changes.
2024-03-18 07:47:35 +00:00

470 lines
14 KiB
Plaintext

---
addHowToData: true
---
import DocCardList from '@theme/DocCardList';
import DocCard from '@theme/DocCard';
import Icons from '@theme/Icon';
import LearningPath from '@site/src/components/LearningPath';
# Build a Marketplace
This document guides you through the different documentation resources that will help you build a marketplace with Medusa.
## Overview
A marketplace is an online commerce store that allows different vendors to sell their products within the same commerce system. Customers can purchase products from any of these vendors, and vendors can manage their orders separately.
<LearningPath pathName="marketplace" />
---
## Associate Entities with Stores
:::tip
Entities represent tables in the database.
:::
By default, entities like users, products, or orders aren't associated with a store, as it's assumed there's one store in Medusa. For a marketplace, each of these entities should be associated with their respective stores.
To associate these entities with the `Store` entity, you need to extend and customize entities created in the Medusa core package `@medusajs/medusa`, such as the `User` entity, to add a relation to the `Store` entity.
<DocCard item={{
type: 'link',
href: '/development/entities/extend-entity',
label: 'Extend an Entity',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to extend an entity in Medusa.',
}
}} />
<Details>
<Summary>Example: Associate User with Store</Summary>
For example, to associate the `User` entity with the `Store` entity, create the file `src/models/user.ts` with the following content:
```ts title="src/models/user.ts"
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
} from "typeorm"
import {
User as MedusaUser,
} from "@medusajs/medusa"
import { Store } from "./store"
@Entity()
export class User extends MedusaUser {
@Index("UserStoreId")
@Column({ nullable: true })
store_id?: string
@ManyToOne(() => Store, (store) => store.members)
@JoinColumn({ name: "store_id", referencedColumnName: "id" })
store?: Store
}
```
Then, you need to extend the `UserRepository` to point to your extended entity. To do that, create the file `src/repositories/user.ts` with the following content:
```ts title="src/repositories/user.ts"
import { User } from "../models/user"
import {
dataSource,
} from "@medusajs/medusa/dist/loaders/database"
import {
UserRepository as MedusaUserRepository,
} from "@medusajs/medusa/dist/repositories/user"
export const UserRepository = dataSource
.getRepository(User)
.extend({
...Object.assign(
MedusaUserRepository,
{ target: User }
),
})
export default UserRepository
```
Next, you need to create a migration that reflects the changes on the `User` entity in your database. To do that, run the following command to create a migration file:
```bash
npx typeorm migration:create src/migrations/add-user-store-id
```
This creates a file in the `src/migrations` directory of the format `<TIMESTAMP>_add-user-store-id.ts`. Replace the `up` and `down` methods in that file with the methods here:
```ts title="src/migrations/<TIMESTAMP>_add-user-store-id.ts"
// ...
export class AddUserStoreId1681287255173
implements MigrationInterface {
// ...
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" ADD "store_id" character varying`
)
await queryRunner.query(
`CREATE INDEX "UserStoreId" ON "user" ("store_id")`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "public"."UserStoreId"`
)
await queryRunner.query(
`ALTER TABLE "user" DROP COLUMN "store_id"`
)
}
}
```
Finally, to reflect these changes and start using them, `build` your changes and run migrations with the following commands:
```bash npm2yarn
npm run build
npx medusa migrations run
```
You can extend other entities in a similar manner to associate them with a store.
</Details>
---
## Accessing Logged-in User
Throughout your development, you'll likely need access to the logged-in user. For example, you'll need to know which user is logged in to know which store to associate a new product with.
<DocCard item={{
type: 'link',
href: '/development/api-routes/example-logged-in-user',
label: 'Access Logged-in User',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to access the logged-in user throughout your project using a Middleware.',
}
}} />
---
## Customize Data Management Functionalities
After associating entities with stores, you'll need to customize how certain data management functionalities are implemented in the Medusa core package.
For example, when a new user is created, you need to ensure that it's associated either with a new store or with the store of the logged-in user. Another example is associating a new product with the logged-in user's store.
You can customize these functionalities by extending services. Services are classes that contain helper methods specific to an entity. For example, the `UserService` is used to manage functionalities related to the `User` entity, such as creating a user.
You can also extend services if you need to customize a functionality implemented in a service for other reasons.
<DocCard item={{
type: 'link',
href: '/development/services/extend-service',
label: 'Extend a Service',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to extend a service in Medusa',
}
}} />
<Details>
<Summary>Example: Extend User Service</Summary>
You can extend the user service to change how the `create` method is implemented.
To extend the user service, create the file `src/services/user.ts` with the following content:
<!-- eslint-disable prefer-rest-params -->
```ts title="src/services/user.ts"
import { Lifetime } from "awilix"
import {
UserService as MedusaUserService,
} from "@medusajs/medusa"
import { User } from "../models/user"
import {
CreateUserInput as MedusaCreateUserInput,
} from "@medusajs/medusa/dist/types/user"
import StoreRepository from "../repositories/store"
type CreateUserInput = {
store_id?: string
} & MedusaCreateUserInput
class UserService extends MedusaUserService {
static LIFE_TIME = Lifetime.SCOPED
protected readonly loggedInUser_: User | null
protected readonly storeRepository_: typeof StoreRepository
constructor(container, options) {
super(...arguments)
this.storeRepository_ = container.storeRepository
try {
this.loggedInUser_ = container.loggedInUser
} catch (e) {
// avoid errors when backend first runs
}
}
async create(
user: CreateUserInput,
password: string
): Promise<User> {
if (!user.store_id) {
const storeRepo = this.manager_.withRepository(
this.storeRepository_
)
let newStore = storeRepo.create()
newStore = await storeRepo.save(newStore)
user.store_id = newStore.id
}
return await super.create(user, password)
}
}
export default UserService
```
In the `create` method of this extended service, you create a new store if the user being created doesn't have a store associated with it.
You can then test out your customization by running the `build` command and starting the backend:
```bash
npm run build
npx medusa develop
```
</Details>
---
## Listening to Events
While implementing your marketplace, you'll typically need to listen to certain events then perform actions asynchronously. For example, you can listen to the `order.placed` event and, when triggered, create child orders of the order, separating ordered items by their associated store.
To listen to events, you need to create Subscribers that subscribe a handler function to an event. In that handler function, you can implement the desired functionality.
<DocCard item={{
type: 'link',
href: '/development/events/create-subscriber',
label: 'Create a Subscriber',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a subscriber in Medusa.',
}
}} />
<Details>
<Summary>Example: Listen to Order Created Event</Summary>
To listen to the `order.placed` event, create the file `src/subscribers/orderNotifier.ts` with the following content:
```ts title="src/subscribers/orderNotifier.ts"
import {
type SubscriberConfig,
type SubscriberArgs,
OrderService,
} from "@medusajs/medusa"
export default async function handleOrderPlaced({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
// TODO perform functionality
}
export const config: SubscriberConfig = {
event: OrderService.Events.PLACED,
context: {
subscriberId: "order-placed-handler",
},
}
```
This subscribes the `handleOrder` method to be executed whenever the `order.placed` event is emitted.
You can then test out your subscriber by running the `build` command and starting the backend:
```bash
npm run build
npx medusa develop
```
</Details>
---
## Add Payment and Fulfillment Providers
Payment and fulfillment providers can be added through plugins or directly in your project. You can either create your own provider, use one of Medusa's official plugins, or use community plugins.
:::note
Payment and fulfillment providers are associated with regions, which are not associated with a store, by default. If you want to allow each store to specify its own payment and fulfillment providers, you'll need to [associate the region with a store](#associate-entities-with-stores).
:::
### Option 1: Create your own providers
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/modules/carts-and-checkout/backend/add-payment-provider',
label: 'Create a Payment Processor',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a payment processor.',
}
},
{
type: 'link',
href: '/modules/carts-and-checkout/backend/add-fulfillment-provider',
label: 'Create a Fulfillment Provider',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a fulfillment provider.',
}
},
]} />
### Option 2: Install a Plugin
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/plugins/overview',
label: 'Install an Official Medusa Plugin',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Check out available Medusa plugins to install.',
}
},
{
type: 'link',
href: 'https://medusajs.com/plugins/',
label: 'Install a Community Plugin',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Check out available community plugins to install.',
}
},
]} />
---
## Customize Admin
As you add marketplace features to your store, you'll most likely need to customize the admin to provide an interface to manage these features.
Medusa's [admin plugin](../admin/quickstart.mdx) can be extended to add widgets or new blocks to existing pages, add UI routes to add new pages, or add setting pages.
<DocCardList colSize={4} items={[
{
type: 'link',
href: '/admin/widgets',
label: 'Create Admin Widget',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to add widgets into existing admin pages.',
}
},
{
type: 'link',
href: '/admin/routes',
label: 'Create Admin UI Routes',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to add new pages to your Medusa Admin.',
}
},
{
type: 'link',
href: '/admin/setting-pages',
label: 'Create Admin Setting Page',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to add new page to the Medusa Admin settings.',
}
},
]} />
---
## Implement Role-Based Access Control
In your marketplace, you may need to implement role-based access control (RBAC) within stores. This will restrict some users' permissions to specified functionalities or API Routes.
If you want to implement this functionality, you can follow the RBAC recipe.
<DocCard item={{
type: 'link',
href: '/modules/users/backend/rbac',
label: 'Implement Role-Based Access Control',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to implement role-based access control (RBAC) in Medusa.',
}
}} />
---
## Build a Storefront
Medusa provides a Next.js Starter Template that you can use with Medusa. Since you've customized your Medusa project, you'll need to either customize the existing Next.js Starter Template, or create a custom storefront.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/starters/nextjs-medusa-starter',
label: 'Option 1: Use Next.js Starter Template',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Install the Next.js Starter Template to customize it.',
}
},
{
type: 'link',
href: '/storefront/roadmap',
label: 'Option 2: Build Custom Storefront',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Find useful resources to build your own storefront.',
}
},
]} />
---
## Deploy Marketplace
Our documentation includes deployment guides for a basic Medusa backend. You should be able to follow it to deploy your customized marketplace, as well.
<DocCard item={{
type: 'link',
href: '/deployments/server',
label: 'Deploy Backend',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to deploy your marketplace backend to different hosting providers.',
}
}} />
---
## Additional Development
You can find other resources for your marketplace development in the [Medusa Development section](../development/overview.mdx) of this documentation.