export const metadata = {
  title: `${pageNumber} Example: Module Relationship`,
}

# {metadata.title}

This chapter gives an example of adding a relationship from your custom module to the Product Module.

<Note>

The example in this chapter uses the same `hello` module with the `HelloModuleService` from previous chapters.

</Note>

## Create CustomProductData Data Model

Create the file `src/modules/hello/models/custom-product-data.ts` with the following content:

```ts title="src/modules/hello/models/custom-product-data.ts"
import { generateEntityId } from "@medusajs/utils"
import { 
  BeforeCreate, 
  Entity, 
  OnInit, 
  PrimaryKey, 
  Property,
} from "@mikro-orm/core"

@Entity()
export class CustomProductData {
  @PrimaryKey({ columnType: "text" })
  id!: string

  @Property({ columnType: "text" })
  custom_field: string

  @Property({ columnType: "text", nullable: true })
  product_id?: string

  @BeforeCreate()
  onCreate() {
    this.id = generateEntityId(this.id, "cpd")
  }

  @OnInit()
  OnInit() {
    this.id = generateEntityId(this.id, "cpd")
  }
}
```

This creates a new data model `CustomProductData` to the `hello` module that stores custom fields related to the `Product` data model in the Product Module

### Add Data Model in MikroORM Configurations

Add your new data model to the MikroORM configurations file at `src/modules/hello/mikro-orm.config.dev.ts`:

```ts title="src/modules/hello/mikro-orm.config.dev.ts"
// other imports...
import { CustomProductData } from "./models/custom-product-data"

module.exports = {
  entities: [
    // other data models...
    CustomProductData,
  ],
  // ...
}
```

### Add Data Model to Module Definition

In your module definition at `src/modules/hello/index.ts`, add the data model to the `models` object:

```ts title="src/modules/hello/index.ts"
// other imports...
import { CustomProductData } from "./models/custom-product-data"

const models = {
  // other models...
  CustomProductData,
}

// ...

const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
  moduleModels: models,
  // ...
})

const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
  moduleModels: Object.values(models),
  // ...
})

// ...
```

This ensures that the model is passed to the container and connection loader along with other models.

### Add Data Model Alias in Joiner Configurations

Add an alias for the data model in the joiner configurations defined in `src/modules/hello/joiner-config.ts`:

```ts title="src/modules/hello/joiner-config.ts"
// other imports...
import { ModuleJoinerConfig } from "@medusajs/types"
import { CustomProductData } from "./models/custom-product-data"

const joinerConfig: ModuleJoinerConfig = {
  // ...,
  alias: [
    // other aliases
    {
      name: ["custom_product_data"],
      args: {
        entity: CustomProductData.name,
        methodSuffix: "CustomProductDatas",
      },
    },
  ],
}
```

### Create Types

Create the file `src/types/hello/custom-product-data.ts` with the following types:

```ts title="src/types/hello/custom-product-data.ts"
export type CustomProductDataDTO = {
  id: string
  custom_field: string
  product_id?: string
}

export type CreateCustomProductDataDTO = {
  custom_field: string
  product_id?: string
}

export type UpdateCustomProductDataDTO = {
  custom_field?: string
  product_id?: string
}

```

You’ll use those in the service next.

### Add Data Model to Main Module Service

In the main module service defined at `src/modules/hello/service.ts`, make the following changes to generate and add methods for the new `CustomProductData` model:

export const serviceHighlights = [
  ["10", "customProductDataService", "Inject the generated service for the `CustomProductData` data model."],
  ["16", "CustomProductData", "Specify the expected input/output type of the data model."],
  ["23", "CustomProductData", "Specify the `CustomProductData` data model to generate methods for it."],
  ["49", "", "Set the `customProductDataService_` class field to the injected `customProductDataService` service."],
  ["55", "createCustomProductData", "Add a method that creates a `CustomProductData` record."]
]

```ts title="src/modules/hello/service.ts" highlights={serviceHighlights}
// other imports...
import { CustomProductData } from "./models/custom-product-data"
import { 
  CreateCustomProductDataDTO, 
  CustomProductDataDTO,
} from "../../types/hello/custom-product-data"

type InjectedDependencies = {
  // other injected dependencies...
  customProductDataService:
    ModulesSdkTypes.InternalModuleService<any>
}

type AllModelsDTO = {
  // other model DTOs...
  CustomProductData: {
    dto: CustomProductDataDTO
  }
}

const generateMethodsFor = [
  // other data models generating methods for...
  CustomProductData,
]

// ...

class HelloModuleService extends ModulesSdkUtils
  .abstractModuleServiceFactory<
    InjectedDependencies,
    MyCustomDTO,
    AllModelsDTO
  >(MyCustom, generateMethodsFor) {
    // ...
    protected customProductDataService_: 
      ModulesSdkTypes.InternalModuleService<CustomProductData>
      
    constructor(
      { 
        // other injected dependencies...
        customProductDataService,
      }: InjectedDependencies,
      protected readonly moduleDeclaration: 
        InternalModuleDeclaration
    ) {
      // @ts-ignore
      super(...arguments)
      // ...
      this.customProductDataService_ = customProductDataService
    }
    
    // other methods...
    
    @InjectTransactionManager("baseRepository_")
    async createCustomProductData(
      data: CreateCustomProductDataDTO,
      @MedusaContext() context: Context = {}
    ): Promise<CustomProductDataDTO> {
      const customProductData = await this.customProductDataService_.create(
        data, 
        context
      )

      return this.baseRepository_.serialize<CustomProductDataDTO>(
        customProductData
      )
    }
  }
```

In the main service you make the following changes:

1. Add `customProductDataService` to the list of injected dependencies.
2. Add the expected input/output type of `CustomProductData` into the `AllModelsDTO` type, which is passed as the third-argument to the `abstractModuleServiceFactory` function.
3. Add the data model to the `generateMethodsFor` array, which is passed as a second parameter to the `abstractModuleServiceFactory` function.
4. Add a `customProductDataService_` field to the `HelloModuleService` and set its value in the constructor to the injected `customProductDataService`.
5. Create a new method  `createCustomProductData` that creates a `CustomProductData` record.

### Generate Migration

Use the following command to generate the migration file:

```bash
npx cross-env MIKRO_ORM_CLI=./src/modules/hello/mikro-orm.config.dev.ts mikro-orm migration:create
```

### Run Migration

Run the following commands to run the latest migration:

```bash npm2yarn
npm run build
npx medusa migrations run
```

---

## Add Relationship

To add the relationship from the `hello` module to the Product Module, make the following change in `src/modules/hello/joiner-config.ts`:

```ts title="src/modules/hello/joiner-config.ts"
// other imports...
import { ModuleJoinerConfig } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"

const joinerConfig: ModuleJoinerConfig = {
  // ...
  relationships: [
    {
      serviceName: Modules.PRODUCT,
      primaryKey: "id",
      foreignKey: "product_id",
      alias: "product",
    },
  ],
}
```

This adds a new relationship in the joiner configuration of the `hello` module. The relationship references the `product` alias in the Product Module, which is the alias of the `Product` data model in that module.

The `product_id` field in your module’s data models references the `id` field of the `Product` data model.

---

## Implement Create API Route

In this section, you’ll implement an API route that creates a product (for simplicity) and then creates a `CustomProductData` record that references that product.

<Note title="Good to know">

This example creates a product in a store route to simplify testing the relationship. In a realistic use case, only create products under the `/admin` prefix to ensure that the user is an authenticated admin.

</Note>

Create the file `src/api/store/custom-product-data/route.ts` with the following content:

export const apiRouteHighlights = [
  ["37", "remoteQuery", "Resolve the remote query function from the Medusa container."],
  ["44", "", "Create a product using the parameters in the request body."],
  ["48", "", "Create a `CustomProductData` record with a reference to the created product's ID."],
  ["54", "remoteQueryObjectFromString", "Create the query to pass to the remote query function."],
  ["59", '"product.title"', "Retrieve the product title among the `CustomProductData` record's data."],
  ["60", '"product.handle"', "Retrieve the product handle among the `CustomProductData` record's data."],
  ["63", "", "Filter fetched records by the ID of the created record."],
  ["67", "", "Run the query with the remote query function."],
  ["70", "", "Return the created `CustomProductData` record in the response."]
]

```ts title="src/api/store/custom-product-data/route.ts" highlights={apiRouteHighlights}
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
import { 
  CreateCustomProductDataDTO,
} from "../../../types/hello/custom-product-data"
import HelloModuleService from "../../../modules/hello/service"
import type { 
  IProductModuleService, 
  CreateProductDTO,
} from "@medusajs/types"
import { 
  remoteQueryObjectFromString,
  ContainerRegistrationKeys,
} from "@medusajs/utils"
import type { 
  RemoteQueryFunction,
} from "@medusajs/modules-sdk"

type CreateCustomProductDataReq = 
  CreateCustomProductDataDTO & {
    product_data: CreateProductDTO
  }

export async function POST(
  req: MedusaRequest<CreateCustomProductDataReq>,
  res: MedusaResponse
): Promise<void> {
  const helloModuleService: HelloModuleService = 
    req.scope.resolve(
      "helloModuleService"
    )

  const productModuleService: IProductModuleService = 
    req.scope.resolve(
      "productModuleService"
    )

  const remoteQuery: RemoteQueryFunction = req.scope.resolve(
    ContainerRegistrationKeys.REMOTE_QUERY
  )

  // skipping validation for simplicity
  const { product_data: productData, ...rest } = req.body

  const product = await productModuleService.create(
    productData as CreateProductDTO
  )

  const customProductData = await helloModuleService
    .createCustomProductData({
      ...rest,
      product_id: product.id,
    })

  const query = remoteQueryObjectFromString({
    entryPoint: "custom_product_data",
    fields: [
      "id", 
      "custom_field", 
      "product.title",
      "product.handle",
    ],
    variables: {
      id: customProductData.id,
    },
  })

  const results = await remoteQuery(query)

  res.json({
    custom_product_data: results[0],
  })
}
```

This API route accepts the necessary request body parameters to create a `CustomProductData` record. It also accepts a `product_data` request body parameter to create the product to be referenced by the `CustomProductData` record.

In the API route handler, you create the product and then create the `CustomProductData` record.

Then, you use the remote query function, resolved from the Medusa container, to fetch the created `CustomProductData` record (filtering by its ID) with the product it references. You can add more product-related fields to the `fields` array passed to `remoteQueryObjectFromString`.

Finally, you return the data returned by the `remoteQuery` function.

### Test Create API Route

To test the API route, start the Medusa application:

```bash npm2yarn
npm run dev
```

Then, send a `POST` request to the `/store/custom-product-data` route:

```bash
curl --location 'http://localhost:9000/store/custom-product-data' \
--header 'Content-Type: application/json' \
--data '{
    "product_data": {
        "title": "Pants"
    },
    "custom_field": "nice shirt"
}'
```

You’ll receive the following response:

```json
{
    "custom_product_data": {
        "id": "cpd_01HWWDD260T0CS64WAF8CVHQ9Y",
        "custom_field": "nice shirt",
        "product_id": "prod_01HWWDD25VPQNNZJ57PFTX2JHE",
        "product": {
            "title": "Shoes",
            "handle": "shoes",
            "id": "prod_01HWWDD25VPQNNZJ57PFTX2JHE"
        }
    }
}
```