Open source ecommerce platform for multi-vendor marketplaces
Tags: javascript, webdev, tutorial, opensource
Published: 2022-03-09 12:47:49

**_🚨 The content of this tutorial may be outdated. You can instead check out the full code for the series in [this GitHub Repository](https://github.com/shahednasser/medusa-marketplace-tutorial).🚨_** 

[Medusa](https://www.medusajs.com) is an open source headless commerce platform that allows you to create your own store in a matter of minutes. Part of what makes Medusa a good choice for your ecommerce store is its extensibility. **Now, it is also possible to create multi-vendor marketplaces using Medusa**.

To make things easier for our open source community, [Adrien de Peretti](https://github.com/adrien2p), one of our amazing contributors, created a Medusa module that allows you to extend anything and everything you want.

> "I've been looking for an e-commerce solution that could provide me with some core features while being fully customisable... After some research, where I found that none of the present solutions could provide what I needed, I chose Medusa as it provided me with many of the needed features while being easy to extend. I ended up loving the community atmosphere, especially the proximity with the team, and have been helping those in the community looking for a similar fully-customisable solution by sharing a part of my private project. This is how the medusa-extender was born." — Adrien de Peretti
> 

In this tutorial, you’ll learn how to install and set up the Medusa Extender module on your Medusa server. You’ll then learn how to use its customization abilities to create a marketplace in your store! The marketplace will have multiple stores or vendors, and each of these stores will be able to add its own products. This tutorial will be the first part of a series that will explore all aspects of creating a marketplace.

## What is Medusa Extender

[Medusa Extender](https://github.com/adrien2p/medusa-extender) is an NPM package that you can add to your Medusa store to extend or customize its functionalities. The scope of its customization entails Entities, Repositories, Services, and more.

The Medusa Extender has many use cases aside the marketplace functionality. It can be used in many other use cases, such as adding custom fields, listening to events to perform certain actions like sending emails, customizing Medusa’s validation of request parameters, and more.

## What You’ll Be Creating

In this article and the following parts of this series, you’ll learn how to create a marketplace using Medusa and Medusa Extender. A marketplace is an online store that allows multiple vendors to add their products and sell them.

A marketplace has a lot of features, including managing a vendor's own orders and settings. This part of the tutorial will only showcase how to create stores for each user and attach the products they create to that store.

## Code for This Tutorial

If you want to follow along you can find the code for this tutorial in [this repository](https://github.com/shahednasser/medusa-marketplace-tutorial/tree/part-1).

Alternatively, if you want to install the marketplace into your existing Medusa store, you can install the [Medusa Marketplace plugin](https://github.com/shahednasser/medusa-marketplace). This plugin is created with the code from this tutorial and will be updated with every new part of this series released.

## Prerequisites

Before you follow along with this tutorial, make sure you have:

1. A Medusa server instance was installed. You can follow along with our easy [quickstart guide](https://docs.medusajs.com/quickstart/quick-start) to learn how you can do that.
2. [PostgreSQL](https://www.postgresql.org/download/) installed and your Medusa server connected to it.
3. [Redis](https://redis.io/download) installed and your Medusa server connected to it.

## Building the Marketplace

### Project Setup

In the directory that holds your Medusa server, start by installing Medusa Extender using NPM:

```bash
npm i medusa-extender
```

It’s recommended that you use TypeScript in your project to get the full benefits of Medusa-Extender. To do that, create the file `tsconfig.json` in the root of the Medusa project with the following content:

```json
{
  "compilerOptions": {
    "module": "CommonJS",
    "declaration": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",
    "target": "es2017",
    "sourceMap": true,
    "skipLibCheck": true,
    "allowJs": true,
    "outDir": "dist",
    "rootDir": ".",
    "esModuleInterop": true
  },
  "include": ["src", "medusa-config.js"],
  "exclude": ["dist", "node_modules", "**/*.spec.ts"]
}
```

Next, update the `scripts` key in `package.json` with the following content:

```json
"scripts": {
    "seed": "medusa seed -f ./data/seed.json",
    "build": "rm -rf dist && tsc",
    "start": "npm run build && node dist/src/main.js",
  },
```

These scripts will ensure that your TypeScript files will be transpiled before Medusa is run.

Then, create the file `main.ts` in the directory `src` with the following content:

```tsx
import { Medusa } from 'medusa-extender';
import express = require('express');

async function bootstrap() {
    const expressInstance = express();
    
    await new Medusa(__dirname + '/../', expressInstance).load([]);
    
    expressInstance.listen(9000, () => {
        console.info('Server successfully started on port 9000');
    });
}

bootstrap();
```

This file will make sure to load all the customizations you’ll add next when you run your Medusa server.

Now, Medusa Extender is fully integrated into your Medusa instance and you can start building the Marketplace.

### Customize the Store Entity

You’ll start by customizing the Store entity. You’ll need to use it later on to add relations between the store entity and the users and products entities.

By convention, customizations using Medusa Extender are organized in a module-like structure. However, this is completely optional.

In the `src` directory, create the directory `modules` in which you’ll store all the customizations in.

Then, create the directory `store` inside the `modules` directory. The `store` directory will hold all customizations related to the Store.

**Create a Store Entity**

Create the file `src/modules/store/entities/store.entity.ts` with the following content:

```tsx
import { Store as MedusaStore } from '@medusajs/medusa/dist';
import { Entity, JoinColumn, OneToMany } from 'typeorm';
import { Entity as MedusaEntity } from 'medusa-extender';

@MedusaEntity({ override: MedusaStore })
@Entity()
export class Store extends MedusaStore {
	//TODO add relations
}
```

This uses the decorator `@Entity` from `medusa-extender` to customize Medusa’s `Store` entity. You create a `Store` class that extends Medusa’s Store entity (imported as `MedusaStore` ).

You’ll, later on, edit this entity to add the relations between the store and users and products.

**Create a Store Repository**

Next, you need to override Medusa’s `StoreRepository`. This repository will return Medusa’s `Store` entity. So, you need to override it to make sure it returns your `Store` entity that you just created.

Create the file `src/modules/store/repositories/store.repository.ts` with the following content:

```tsx
import { EntityRepository } from 'typeorm';
import { StoreRepository as MedusaStoreRepository } from '@medusajs/medusa/dist/repositories/store';
import { Repository as MedusaRepository, Utils } from 'medusa-extender';
import { Store } from '../entities/store.entity';

@MedusaRepository({ override: MedusaStoreRepository })
@EntityRepository(Store)
export default class StoreRepository extends Utils.repositoryMixin<Store, MedusaStoreRepository>(MedusaStoreRepository) {
}
```

**Create the Store Module**

For now, these are the only files you’ll add for the store. You can create the Store module using these files.

Create the file `src/modules/store/store.module.ts` with the following content:

```tsx
import { Module } from 'medusa-extender';
import { Store } from './entities/store.entity';
import StoreRepository from './repositories/store.repository';

@Module({
	imports: [Store, StoreRepository],
})
export class StoreModule {}
```

This uses the `@Module` decorator from `medusa-extender` and imports the 2 classes you created.

The last thing left is to import this module and use it with Medusa. In `src/main.ts` import `StoreModule` at the beginning of the file:

```tsx
import { StoreModule } from './modules/store/store.module';
```

Then, add the `StoreModule` in the array passed as a parameter to `Medusa.load`:

```tsx
await new Medusa(__dirname + '/../', expressInstance).load([
	StoreModule
]);
```

This is all that you’ll do for now in the Store module. In the next sections, you’ll be adding more classes to it as necessary.

### Customize the User Entity

In this section, you’ll customize the user entity mainly to link the user to a store.

**Create the User Entity**

Create the directory `user` inside the `modules` directory and create the file `src/modules/user/entities/user.entity.ts` with the following content:

```tsx
import { User as MedusaUser } from '@medusajs/medusa/dist';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Entity as MedusaEntity } from 'medusa-extender';
import { Store } from '../../store/entities/store.entity';

@MedusaEntity({ override: MedusaUser })
@Entity()
export class User extends MedusaUser {
	@Index()
	@Column({ nullable: false })
	store_id: string;

	@ManyToOne(() => Store, (store) => store.members)
	@JoinColumn({ name: 'store_id' })
	store: Store;
}
```

This class will add an additional column `store_id` of type string and will add a relation to the `Store` entity.

To add the new column to the `user` table in the database, you need to create a Migration file. Create the file `src/modules/user/user.migration.ts` with the following content:

```tsx
import { Migration } from 'medusa-extender';
import { MigrationInterface, QueryRunner } from 'typeorm';

@Migration()
export default class addStoreIdToUser1644946220401 implements MigrationInterface {
    name = 'addStoreIdToUser1644946220401';
    
    public async up(queryRunner: QueryRunner): Promise<void> {
      const query = `ALTER TABLE public."user" ADD COLUMN IF NOT EXISTS "store_id" text;`;
      await queryRunner.query(query);
    }
    
    public async down(queryRunner: QueryRunner): Promise<void> {
      const query = `ALTER TABLE public."user" DROP COLUMN "store_id";`;
      await queryRunner.query(query);
    }
}
```

The migration is created using the `@Migration` decorator from `medusa-extender`. Notice that the migration name should end with a JavaScript timestamp based on `typeorm`'s conventions.

The `up` method is run if the migration hasn’t been run before. It will add the column `store_id` to the table `user` if it doesn’t exist.

You’ll also need to add the relation between the Store and the User entities in `src/modules/store/entities/store.entity.ts` . Replace the `//TODO` with the following:

```tsx
@OneToMany(() => User, (user) => user.store)
@JoinColumn({ name: 'id', referencedColumnName: 'store_id' })
members: User[];
```

Make sure to import the `User` entity at the beginning of the file:

```tsx
import { User } from '../../user/entities/user.entity';
```

**Create the User Repository**

Next, you need to override Medusa’s `UserRepository`. Create the file `src/modules/user/repositories/user.repository.ts` with the following content:

```tsx
import { UserRepository as MedusaUserRepository } from "@medusajs/medusa/dist/repositories/user";
import { Repository as MedusaRepository, Utils } from "medusa-extender";
import { EntityRepository } from "typeorm";
import { User } from "../entities/user.entity";

@MedusaRepository({ override: MedusaUserRepository })
@EntityRepository(User)
export default class UserRepository extends Utils.repositoryMixin<User, MedusaUserRepository>(MedusaUserRepository) {
}
```

**Create the User Service**

Next, you need to override Medusa’s `UserService` class.  Create the file `src/modules/user/services/user.service.ts` with the following content:

```tsx
import { Service } from 'medusa-extender';
import { EntityManager } from 'typeorm';
import EventBusService from '@medusajs/medusa/dist/services/event-bus';
import { FindConfig } from '@medusajs/medusa/dist/types/common';
import { UserService as MedusaUserService } from '@medusajs/medusa/dist/services';
import { User } from '../entities/user.entity';
import UserRepository from '../repositories/user.repository';
import { MedusaError } from 'medusa-core-utils';

type ConstructorParams = {
    manager: EntityManager;
    userRepository: typeof UserRepository;
    eventBusService: EventBusService;
};

@Service({ override: MedusaUserService })
export default class UserService extends MedusaUserService {
    private readonly manager: EntityManager;
    private readonly userRepository: typeof UserRepository;
    private readonly eventBus: EventBusService;

    constructor(private readonly container: ConstructorParams) {
        super(container);
        this.manager = container.manager;
        this.userRepository = container.userRepository;
        this.eventBus = container.eventBusService;

    }

    public async retrieve(userId: string, config?: FindConfig<User>): Promise<User> {
        const userRepo = this.manager.getCustomRepository(this.userRepository);
        const validatedId = this.validateId_(userId);
        const query = this.buildQuery_({ id: validatedId }, config);

        const user = await userRepo.findOne(query);

        if (!user) {
            throw new MedusaError(MedusaError.Types.NOT_FOUND, `User with id: ${userId} was not found`);
        }

        return user as User;
    }
}
```

This uses the `@Service` decorator from `medusa-extender` to override Medusa’s `UserService`. The class you create to override it will extend `UserService`.

This new class overrides the `retrieve` method to ensure that the user returned is the new User entity class you created earlier.

**Create a User Middleware**

The `loggedInUser` is not available natively in Medusa. You’ll need to create a Middleware that, when a request is authenticated, registers the logged-in User within the scope.

Create the file `src/modules/user/middlewares/loggedInUser.middleware.ts` with the following content:

```tsx
import { MedusaAuthenticatedRequest, MedusaMiddleware, Middleware } from 'medusa-extender';
import { NextFunction, Response } from 'express';

import UserService from '../../user/services/user.service';

@Middleware({ requireAuth: true, routes: [{ method: "all", path: '*' }] })
export class LoggedInUserMiddleware implements MedusaMiddleware {
    public async consume(req: MedusaAuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
        if (req.user && req.user.userId) {
            const userService = req.scope.resolve('userService') as UserService;
            const loggedInUser = await userService.retrieve(req.user.userId, {
                select: ['id', 'store_id'],
            });
            
            req.scope.register({
                loggedInUser: {
                    resolve: () => loggedInUser,
                },
            });
        }
        next();
    }
}
```

You can use the `@Middleware` decorator from `medusa-extender` to create a Middleware that runs on specific requests. This Middleware is run when the request is received from an authenticated user, and it runs for all paths (notice the use of `path: '*'` ) and for all types of requests (notice the use of `method: "all"`).

Inside the middleware, you retrieve the current user ID from the request, then retrieve the user model and register it in the scope so that it can be accessed from services.

> This approach is simplified for the purpose of this tutorial. However, it makes more sense to include this middleware in a separate `auth` module. Whether you include this middleware in the `user` module or the `auth` middleware will not affect its functionality.
> 

**Create a Store Service to Handle User Insert Events**

You need to ensure that when a user is created, a store is associated with it. You can do that by listening to the User-created event and creating a new store for that user. You’ll add this event handler in a `StoreService`.

Create the file `src/modules/store/services/store.service.ts` with the following content:

```tsx
import { StoreService as MedusaStoreService } from '@medusajs/medusa/dist/services';
import { EntityManager } from 'typeorm';
import { CurrencyRepository } from '@medusajs/medusa/dist/repositories/currency';
import { Store } from '../entities/store.entity';
import { EntityEventType, Service, MedusaEventHandlerParams, OnMedusaEntityEvent } from 'medusa-extender';
import { User } from '../../user/entities/user.entity';
import EventBusService from '@medusajs/medusa/dist/services/event-bus';
import StoreRepository from '../repositories/store.repository';

interface ConstructorParams {
	loggedInUser: User;
	manager: EntityManager;
	storeRepository: typeof StoreRepository;
	currencyRepository: typeof CurrencyRepository;
	eventBusService: EventBusService;
}

@Service({ override: MedusaStoreService, scope: 'SCOPED' })
export default class StoreService extends MedusaStoreService {
	private readonly manager: EntityManager;
	private readonly storeRepository: typeof StoreRepository;

	constructor(private readonly container: ConstructorParams) {
		super(container);
		this.manager = container.manager;
		this.storeRepository = container.storeRepository;
	}

	withTransaction(transactionManager: EntityManager): StoreService {
		if (!transactionManager) {
			return this;
		}

		const cloned = new StoreService({
			...this.container,
			manager: transactionManager,
		});

		cloned.transactionManager_ = transactionManager;

		return cloned;
	}

	@OnMedusaEntityEvent.Before.Insert(User, { async: true })
	public async createStoreForNewUser(
		params: MedusaEventHandlerParams<User, 'Insert'>
	): Promise<EntityEventType<User, 'Insert'>> {
		const { event } = params;
		const createdStore = await this.withTransaction(event.manager).createForUser(event.entity);
		if (!!createdStore) {
			event.entity.store_id = createdStore.id;
		}
		return event;
	}

	public async createForUser(user: User): Promise<Store | void> {
		if (user.store_id) {
			return;
		}
		const storeRepo = this.manager.getCustomRepository(this.storeRepository);
		const store = storeRepo.create() as Store;
		return storeRepo.save(store);
	}

	public async retrieve(relations: string[] = []) {
		if (!this.container.loggedInUser) {
			return super.retrieve(relations);
		}

		const storeRepo = this.manager.getCustomRepository(this.storeRepository);
		const store = await storeRepo.findOne({
			relations,
			join: { alias: 'store', innerJoin: { members: 'store.members' } },
			where: (qb) => {
				qb.where('members.id = :memberId', { memberId: this.container.loggedInUser.id });
			},
		});

		if (!store) {
			throw new Error('Unable to find the user store');
		}

		return store;
	}
}
```

`@OnMedusaEntityEvent.Before.Insert` is used to add a listener to an insert event on an entity, which in this case is the `User` entity. Inside the listener, you create the user using the `createForUser` method. This method just uses the `StoreRepository` to create a store.

You also add a helper event `retrieve` to retrieve the store that belongs to the currently logged-in user.

Notice the use of `scope: 'SCOPED'` in the `@Service` decorator. This will allow you to access the logged in user you registered earlier in the scope.

You’ll need to import this new class into the `StoreModule`. In `src/modules/store/store.module.ts` add the following import at the beginning:

```tsx
import StoreService from './services/store.service';
```

Then, add the `StoreService` to the `imports` array passed to `@Module` :

```tsx
imports: [Store, StoreRepository, StoreService],
```

**Create a User Subscriber**

For the event listener to work, you need to first emit this event in a subscriber. The event will be emitted before a `User` is inserted. Create the file `src/modules/user/subscribers/user.subscriber.ts` with the following content:

```tsx
import { Connection, EntitySubscriberInterface, EventSubscriber, InsertEvent } from 'typeorm';
import { eventEmitter, Utils as MedusaUtils, OnMedusaEntityEvent } from 'medusa-extender';
import { User } from '../entities/user.entity';

@EventSubscriber()
export default class UserSubscriber implements EntitySubscriberInterface<User> {
	static attachTo(connection: Connection): void {
		MedusaUtils.attachOrReplaceEntitySubscriber(connection, UserSubscriber);
	}

	public listenTo(): typeof User {
		return User;
	}

	public async beforeInsert(event: InsertEvent<User>): Promise<void> {
		return await eventEmitter.emitAsync(OnMedusaEntityEvent.Before.InsertEvent(User), {
			event,
			transactionalEntityManager: event.manager,
		});
	}
}
```

This will create a subscriber using the `EventSubscriber` decorator from `typeorm`. Then, before a user is inserted the `OnMedusaEntityEvent.Before.InsertEvent` event from `medusa-extender` is emitted, which will trigger creating the store.

To register the subscriber, you need to create a middleware that registers it. Create the file `src/modules/user/middlewares/userSubscriber.middleware.ts` with the following content:

```tsx
import {
  MEDUSA_RESOLVER_KEYS,
  MedusaAuthenticatedRequest,
  MedusaMiddleware,
  Utils as MedusaUtils,
  Middleware
} from 'medusa-extender';
import { NextFunction, Response } from 'express';

import { Connection } from 'typeorm';
import UserSubscriber from '../subscribers/user.subscriber';

@Middleware({ requireAuth: false, routes: [{ method: "post", path: '/admin/users' }] })
export class AttachUserSubscriberMiddleware implements MedusaMiddleware {
    public async consume(req: MedusaAuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
      const { connection } = req.scope.resolve(MEDUSA_RESOLVER_KEYS.manager) as { connection: Connection };
        MedusaUtils.attachOrReplaceEntitySubscriber(connection, UserSubscriber);
        return next();
    }
}
```

This will register the subscriber when a `POST` request is sent to `/admin/users`, which creates a new user.

**Create a User Router**

The last customization left is an optional one. By default, Medusa’s create user endpoint requires you to be authenticated as an admin. In a marketplace use case, you might want users to register on their own and create their own stores. If this is not the case for you, you can skip creating the following class.

Medusa Extender allows you to also override routes in Medusa. In this case, you’ll be adding the `/admin/create-user` route to accept non-authenticated requests.

Create the file `src/modules/user/routers/user.router.ts` and add the following content:

```tsx
import { Router } from 'medusa-extender';
import createUserHandler from '@medusajs/medusa/dist/api/routes/admin/users/create-user';
import wrapHandler from '@medusajs/medusa/dist/api/middlewares/await-middleware';

@Router({
    routes: [
        {
            requiredAuth: false,
            path: '/admin/create-user',
            method: 'post',
            handlers: [wrapHandler(createUserHandler)],
        },
    ],
})
export class UserRouter {
}
```

You use the `@Router` decorator from `medusa-extender` to create a router. This router will accept a `routes` array which will either be added or override existing routes in your Medusa server. In this case, you override the `/admin/create-user` route and set `requiredAuth` to false.

To make sure that the `AttachUserSubscriberMiddleware` also runs for this new route (so that the before insert user event handlers run for this new route), make sure to add a new entry to the `routes` array:

```tsx
@Middleware({ requireAuth: false, routes: [{ method: "post", path: '/admin/users' }, { method: "post", path: '/admin/create-user' }] })
```

**Create a User Module**

You’ve added all the customizations necessary to associate a user with their own store. Now, you can create the User module using these files.

Create the file `src/modules/user/user.module.ts` with the following content:

```tsx
import { AttachUserSubscriberMiddleware } from './middlewares/userSubscriber.middleware';
import { LoggedInUserMiddleware } from "./middlewares/loggedInUser.middleware";
import { Module } from 'medusa-extender';
import { User } from './entities/user.entity';
import UserRepository from './repositories/user.repository';
import { UserRouter } from "./routers/user.router";
import UserService from './services/user.service';
import addStoreIdToUser1644946220401 from './user.migration';

@Module({
    imports: [
        User,
        UserService,
        UserRepository,
        addStoreIdToUser1644946220401,
        UserRouter,
        LoggedInUserMiddleware,
        AttachUserSubscriberMiddleware
    ]
})
export class UserModule {}
```

> If you didn’t create the `UserRouter` in the previous step then make sure to remove it from the `imports` array.
> 

The last thing left is to import this Module. In `src/main.ts` import `UserModule` at the beginning of the file:

```tsx
import { UserModule } from './modules/user/user.module';
```

Then, add the `UserModule` in the array passed as a parameter to `Medusa.load`:

```tsx
await new Medusa(__dirname + '/../', expressInstance).load([
        UserModule,
        StoreModule
]);
```

**Test it Out**

You are now ready to test out this customization! In your terminal, run your Medusa server:

```bash
npm start
```

Or using Medusa’s CLI:

```bash
medusa develop
```

After your run your server, you need to use a tool like [Postman](https://www.postman.com) to easily send requests to your server.

If you didn’t add the `UserRouter`, you first need to log in as an admin to be able to add users. You can do that by sending a `POST` request to `localhost:9000/admin/auth`. In the body, you should include the email and password. If you’re using a fresh Medusa install you can use the following credentials:

```json
{
  "email": "admin@medusa-test.com",
  "password": "supersecret"
}
```

Following this request, you can send authenticated requests to the Admin. 

Send a `POST` request to `[localhost:9000/admin/users](http://localhost:9000/admin/users)` to create a new user. In the body, you need to pass the email and password of the new user:

```json
{
  "email": "example@gmail.com",
  "password": "supersecret"
}
```

The request will return a user object with the details of the new user:

![Create User Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ghtsqmjvvqrs09mc2f8.png)

Notice how there’s a `store_id` field now. If you try to create a couple of users, you’ll see that the `store_id` will be different each time.

### Customize the Products Entity

Similar to how you just customized the `User` entity, you need to customize the `Product` entity to also hold the `store_id` with the relationship as well. You’ll then customize the `ProductService` as well as other classes to make sure that, when a product is created, the store ID of the user creating it is attached to it. You’ll also make sure that when the list of products is fetched, only the products that belong to the current user’s store are returned.

**Create a Product Entity**

Create the file `src/modules/product/entities/product.entity.ts` with the following content:

```tsx
import { Product as MedusaProduct } from '@medusajs/medusa/dist';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Entity as MedusaEntity } from 'medusa-extender';
import { Store } from '../../store/entities/store.entity';

@MedusaEntity({ override: MedusaProduct })
@Entity()
export class Product extends MedusaProduct {
	@Index()
	@Column({ nullable: false })
	store_id: string;

	@ManyToOne(() => Store, (store) => store.members)
	@JoinColumn({ name: 'store_id', referencedColumnName: 'id' })
	store: Store;
}
```

This will override Medusa’s `Product` entity to add the `store_id` field and relation to the `Store` entity.

You need to also reflect this relation in the `Store` entity, so, in `src/modules/store/entities/store.entity.ts` add the following code below the relation with the `User` entity you previously added:

```tsx
@OneToMany(() => Product, (product) => product.store)
@JoinColumn({ name: 'id', referencedColumnName: 'store_id' })
products: Product[];
```

Make sure to import the `Product` entity at the beginning of the file:

```tsx
import { Product } from '../../product/entities/product.entity';
```

**Create a Product Migration**

Next, create the file `src/modules/product/product.migration.ts` with the following content:

```tsx
import { MigrationInterface, QueryRunner } from 'typeorm';

import { Migration } from 'medusa-extender';

@Migration()
export default class addStoreIdToProduct1645034402086 implements MigrationInterface {
    name = 'addStoreIdToProduct1645034402086';
    
    public async up(queryRunner: QueryRunner): Promise<void> {
      const query = `ALTER TABLE public."product" ADD COLUMN IF NOT EXISTS "store_id" text;`;
      await queryRunner.query(query);
    }
    
    public async down(queryRunner: QueryRunner): Promise<void> {
      const query = `ALTER TABLE public."product" DROP COLUMN "store_id";`;
      await queryRunner.query(query);
    }
}
```

This will add a migration that will add the `store_id` column to the `product` table.

**Create a Product Repository**

Next, create the file `src/modules/repositories/product.repository.ts` with the following content:

```tsx
import { Repository as MedusaRepository, Utils } from "medusa-extender";

import { EntityRepository } from "typeorm";
import { ProductRepository as MedusaProductRepository } from "@medusajs/medusa/dist/repositories/product";
import { Product } from '../entities/product.entity';

@MedusaRepository({ override: MedusaProductRepository })
@EntityRepository(Product)
export default class ProductRepository extends Utils.repositoryMixin<Product, MedusaProductRepository>(MedusaProductRepository) {
}
```

This will override Medusa’s `ProductRepository` to return your new `Product` entity.

**Create a Product Service**

Now, you’ll add the customization to ensure that only the products that belong to the currently logged-in user are returned when a request is sent.

Since you created the `LoggedInUserMiddleware` earlier, you can have access to the logged-in user from any service through the `container` object passed to the constructor of the service.

Create the file `src/modules/product/services/product.service.ts` with the following content:

```tsx
import { EntityEventType, MedusaEventHandlerParams, OnMedusaEntityEvent, Service } from 'medusa-extender';

import { EntityManager } from "typeorm";
import { ProductService as MedusaProductService } from '@medusajs/medusa/dist/services';
import { Product } from '../entities/product.entity';
import { User } from '../../user/entities/user.entity';
import UserService from '../../user/services/user.service';

type ConstructorParams = {
    manager: any;
    loggedInUser: User;
    productRepository: any;
    productVariantRepository: any;
    productOptionRepository: any;
    eventBusService: any;
    productVariantService: any;
    productCollectionService: any;
    productTypeRepository: any;
    productTagRepository: any;
    imageRepository: any;
    searchService: any;
    userService: UserService;
}

@Service({ scope: 'SCOPED', override: MedusaProductService })
export class ProductService extends MedusaProductService {
    readonly #manager: EntityManager;

    constructor(private readonly container: ConstructorParams) {
        super(container);
        this.#manager = container.manager;
    }

    prepareListQuery_(selector: object, config: object): object {
        const loggedInUser = this.container.loggedInUser
        if (loggedInUser) {
            selector['store_id'] = loggedInUser.store_id
        }

        return super.prepareListQuery_(selector, config);
    }
}
```

This will override the `prepareListQuery` method in Medusa’s `ProductService`, which this new class extends, to get the logged-in user. Then, if the user is retrieved successfully the key `store_id` is added to the `selector` object to filter the products by the user’s `store_id`.

**Create a Product Module**

That’s all the customization you’ll do for now. You just need to import all these files into a Product module.

Create `src/modules/product/product.module.ts` with the following content:

```tsx
import { Module } from 'medusa-extender';
import { Product } from './entities/product.entity';
import ProductRepository from './repositories/product.repository';
import { ProductService } from './services/product.service';
import addStoreIdToProduct1645034402086 from './product.migration';

@Module({
    imports: [
      Product,
      ProductRepository,
      ProductService,
      addStoreIdToProduct1645034402086,
    ]
})
export class ProductModule {}
```

Finally, import the `ProductModule` at the beginning of `src/main.ts`:

```tsx
import { ProductModule } from './modules/product/product.module';
```

And add the `ProductModule` to the array passed to `load` along with `UserModule`:

```tsx
await new Medusa(__dirname + '/../', expressInstance).load([
    UserModule,
    ProductModule,
    StoreModule
]);
```

**Test it Out**

You can go ahead and test it out now. Run the server if it isn’t running already and log in with the user you created earlier by sending the credentials to `localhost:9000/admin/auth`.

After that, send a `GET` request to `localhost:9000/admin/products`. You’ll receive an empty array of products as the current user does not have any products yet.

![Result of Get Products](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3eywmeesxndkqcjoto20.png)

**Create a Product Subscriber**

You’ll now add the necessary customization to attach a store ID to a newly created product.

To listen to the product created event, create the file `src/modules/product/subscribers/product.subscriber.ts` with the following content:

```tsx
import { Connection, EntitySubscriberInterface, EventSubscriber, InsertEvent } from 'typeorm';
import { OnMedusaEntityEvent, Utils, eventEmitter } from 'medusa-extender';

import { Product } from '../entities/product.entity';

@EventSubscriber()
export default class ProductSubscriber implements EntitySubscriberInterface<Product> {
    static attachTo(connection: Connection): void {
        Utils.attachOrReplaceEntitySubscriber(connection, ProductSubscriber);
    }
    
    public listenTo(): typeof Product {
        return Product;
    }
    
    public async beforeInsert(event: InsertEvent<Product>): Promise<void> {
        return await eventEmitter.emitAsync(OnMedusaEntityEvent.Before.InsertEvent(Product), {
            event,
            transactionalEntityManager: event.manager,
        });
    }
}
```

Then, you need to register this Subscriber using Middleware. Create the file `src/modules/product/middlewares/product.middleware.ts` with the following content:

```tsx
import {
  MEDUSA_RESOLVER_KEYS,
  MedusaAuthenticatedRequest,
  MedusaMiddleware,
  Utils as MedusaUtils,
  Middleware
} from 'medusa-extender';
import { NextFunction, Request, Response } from 'express';

import { Connection } from 'typeorm';
import ProductSubscriber from '../subscribers/product.subscriber';

@Middleware({ requireAuth: true, routes: [{ method: 'post', path: '/admin/products' }] })
export default class AttachProductSubscribersMiddleware implements MedusaMiddleware {
    public consume(req: MedusaAuthenticatedRequest | Request, res: Response, next: NextFunction): void | Promise<void> {
        const { connection } = req.scope.resolve(MEDUSA_RESOLVER_KEYS.manager) as { connection: Connection };
        MedusaUtils.attachOrReplaceEntitySubscriber(connection, ProductSubscriber);
        return next();
    }
}
```

This will register the subscriber when a `POST` request is sent to `/admin/products`, which creates a new product.

**Add Event Listener in Product Service**

Next, in `src/modules/product/services/product.service.ts` add the following inside the class:

```tsx
@OnMedusaEntityEvent.Before.Insert(Product, { async: true })
public async attachStoreToProduct(
    params: MedusaEventHandlerParams<Product, 'Insert'>
): Promise<EntityEventType<Product, 'Insert'>> {
    const { event } = params;
    const loggedInUser = this.container.loggedInUser;
    event.entity.store_id = loggedInUser.store_id;
    return event;
}
```

This will listen to the Insert event using the `@OnMedusaEntityEvent` decorator from `medusa-extender`. It will then use the logged-in user and attach the user’s `store_id` to the newly created product.

**Add Middleware to Product Module**

Finally, make sure to import the new middleware at the beginning of `src/modules/product/product.module.ts`:

```tsx
import AttachProductSubscribersMiddleware from './middlewares/product.middleware';
```

Then, add it in the `imports` array passed to `@Module`:

```tsx
imports: [
  Product,
  ProductRepository,
  ProductService,
  addStoreIdToProduct1645034402086,
  AttachProductSubscribersMiddleware
]
```

You’re ready to add products into a store now! Run the server if it’s not running and make sure you’re logged in with the user you created earlier. Then, send a `POST` request to `[localhost:9000/admin/products](http://localhost:9000/admin/products)` with the following body:

```tsx
{
    "title": "my product",
    "options": []
}
```

This is the minimum structure of a product. You can rename the title to anything you want.

After you send the request, you should receive a Product object where you can see the `store_id` is set to the same `store_id` of the user you’re logged in with.

![Add Product Request Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yu8r2bu3jjwwkrijboq7.png)

Now, try sending a `GET` request to `[localhost:9000/admin/products](http://localhost:9000/admin/products)` as you did earlier. Instead of an empty array, you’ll see the product you just added.

![Retrieve Products](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ot6pg7oquie2ihkmsbp.png)

## Testing it Out Using Medusa’s Admin

If you also have a [Medusa Admin](https://github.com/medusajs/admin) instance installed, you can also test this out. Log in with the user you created earlier and you’ll see that you can only see the product they added.

![Admin Dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4fejxl0kyi130bv4wb0.png)

## Conclusion

In this tutorial, you learned the first steps of creating a Marketplace using Medusa and Medusa Extender! In later points, you’ll learn about how you can add settings, manage orders, and more!

Be sure to support [Medusa Extender](https://github.com/adrien2p/medusa-extender) and check the repository out for more details!

Should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via [Discord](https://discord.gg/F87eGuwkTp). You can also contact Adrien `@adrien2p` for more details or help regarding Medusa Extender.