Files
medusa-store/www/apps/docs/content/references/file/classes/file.AbstractFileService.mdx
2024-01-22 18:38:35 +01:00

381 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
displayed_sidebar: core
slug: /development/file-service/create-file-service
---
import ParameterTypes from "@site/src/components/ParameterTypes"
# How to Create a File Service
In this document, youll learn how to create a file service in the Medusa backend and the methods you must implement in it.
## Overview
A file service class is defined in a TypeScript or JavaScript file thats created in the `src/services` directory.
The class must extend the `AbstractFileService` class imported from the `@medusajs/medusa` package.
Based on services naming conventions, the files name should be the slug version of the file services name
without `service`, and the classs name should be the pascal case of the file services name following by `Service`.
For example, create the file `src/services/local-file.ts` with the following content:
```ts title="src/services/local-file.ts"
import { AbstractFileService } from "@medusajs/medusa"
import {
DeleteFileType,
FileServiceGetUploadStreamResult,
FileServiceUploadResult,
GetUploadedFileType,
UploadStreamDescriptorType,
} from "@medusajs/types"
class LocalFileService extends AbstractFileService {
async upload(
fileData: Express.Multer.File
): Promise<FileServiceUploadResult> {
throw new Error("Method not implemented.")
}
async uploadProtected(
fileData: Express.Multer.File
): Promise<FileServiceUploadResult> {
throw new Error("Method not implemented.")
}
async delete(fileData: DeleteFileType): Promise<void> {
throw new Error("Method not implemented.")
}
async getUploadStreamDescriptor(
fileData: UploadStreamDescriptorType
): Promise<FileServiceGetUploadStreamResult> {
throw new Error("Method not implemented.")
}
async getDownloadStream(
fileData: GetUploadedFileType
): Promise<NodeJS.ReadableStream> {
throw new Error("Method not implemented.")
}
async getPresignedDownloadUrl(
fileData: GetUploadedFileType
): Promise<string> {
throw new Error("Method not implemented.")
}
}
export default LocalFileService
```
:::note[Multer Typing]
The examples implement a file service supporting local uploads.
If youre using TypeScript and you're following along with the implementation,
you should install the Multer types package in the root of your Medusa backend to resolve errors within your file service types:
```bash npm2yarn
npm install @types/multer
```
:::
---
## constructor
You can use the `constructor` of your file service to access the different services in Medusa through dependency injection.
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party providers APIs,
you can initialize it in the constructor and use it in other methods in the service.
Additionally, if youre creating your file service as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin,
you can access them in the constructor.
### Example
```ts
// ...
import { Logger } from "@medusajs/medusa"
import * as fs from "fs"
class LocalFileService extends AbstractFileService {
// can also be replaced by an environment variable
// or a plugin option
protected serverUrl = "http://localhost:9000"
protected publicPath = "uploads"
protected protectedPath = "protected-uploads"
protected logger_: Logger
constructor({ logger }: InjectedDependencies) {
// @ts-ignore
super(...arguments)
this.logger_ = logger
// for public uploads
if (!fs.existsSync(this.publicPath)) {
fs.mkdirSync(this.publicPath)
}
// for protected uploads
if (!fs.existsSync(this.protectedPath)) {
fs.mkdirSync(this.protectedPath)
}
}
// ...
}
```
### Parameters
<ParameterTypes parameters={[{"name":"container","type":"[MedusaContainer](../../medusa/types/medusa.MedusaContainer-2.mdx)","description":"An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"config","type":"`Record<string, unknown>`","description":"If this file service is created in a plugin, the plugin's options are passed in this parameter.","optional":true,"defaultValue":"","expandable":false,"children":[]}]} />
___
## Methods
### upload
This method is used to upload a file to the Medusa backend.
#### Example
```ts
class LocalFileService extends AbstractFileService {
// ...
async upload(
fileData: Express.Multer.File
): Promise<FileServiceUploadResult> {
const filePath =
`${this.publicPath}/${fileData.originalname}`
fs.copyFileSync(fileData.path, filePath)
return {
url: `${this.serverUrl}/${filePath}`,
key: filePath,
}
}
// ...
}
```
:::tip
This example does not account for duplicate names to maintain simplicity in this guide. So, an uploaded file can replace another existing file that has the same name.
:::
#### Parameters
<ParameterTypes parameters={[{"name":"fileData","type":"`File`","description":"A [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information).\nThe file is uploaded to a temporary directory by default. Among the files details, you can access the files path in the `path` property of the file object.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} />
#### Returns
<ParameterTypes parameters={[{"name":"Promise","type":"Promise&#60;[FileServiceUploadResult](../../medusa/interfaces/medusa.FileServiceUploadResult.mdx)&#62;","optional":false,"defaultValue":"","description":"The details of the upload's result.","expandable":false,"children":[{"name":"FileServiceUploadResult","type":"[FileServiceUploadResult](../../medusa/interfaces/medusa.FileServiceUploadResult.mdx)","optional":false,"defaultValue":"","description":"Details of a file upload's result.","expandable":false,"children":[{"name":"url","type":"`string`","description":"The file's URL.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"key","type":"`string`","description":"The file's key. This key is used in other operations,\nsuch as deleting a file.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]}]} />
### uploadProtected
This method is used to upload a file to the Medusa backend, but to a protected storage. Typically, this would be used to store files that
shouldnt be accessible by using the files URL or should only be accessible by authenticated users. For example, exported or imported
CSV files.
#### Example
```ts
class LocalFileService extends AbstractFileService {
// ...
async uploadProtected(
fileData: Express.Multer.File
): Promise<FileServiceUploadResult> {
const filePath =
`${this.protectedPath}/${fileData.originalname}`
fs.copyFileSync(fileData.path, filePath)
return {
url: `${this.serverUrl}/${filePath}`,
key: filePath
}
}
// ...
}
```
:::tip
This example does not account for duplicate names to maintain simplicity in this guide. So, an uploaded file can replace another existing file that has the same name.
:::
#### Parameters
<ParameterTypes parameters={[{"name":"fileData","type":"`File`","description":"A [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information).\nThe file is uploaded to a temporary directory by default. Among the files details, you can access the files path in the `path` property of the file object.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} />
#### Returns
<ParameterTypes parameters={[{"name":"Promise","type":"Promise&#60;[FileServiceUploadResult](../../medusa/interfaces/medusa.FileServiceUploadResult.mdx)&#62;","optional":false,"defaultValue":"","description":"The details of the upload's result.","expandable":false,"children":[{"name":"FileServiceUploadResult","type":"[FileServiceUploadResult](../../medusa/interfaces/medusa.FileServiceUploadResult.mdx)","optional":false,"defaultValue":"","description":"Details of a file upload's result.","expandable":false,"children":[{"name":"url","type":"`string`","description":"The file's URL.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"key","type":"`string`","description":"The file's key. This key is used in other operations,\nsuch as deleting a file.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]}]} />
### delete
This method is used to delete a file from storage.
#### Example
```ts
class LocalFileService extends AbstractFileService {
async delete(
fileData: DeleteFileType
): Promise<void> {
fs.rmSync(fileData.fileKey)
}
// ...
}
```
#### Parameters
<ParameterTypes parameters={[{"name":"fileData","type":"[DeleteFileType](../../medusa/interfaces/medusa.DeleteFileType.mdx)","description":"The details of the file to remove.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"`string`","description":"The file's key. When uploading a file, the\nreturned key is used here.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]} />
#### Returns
<ParameterTypes parameters={[{"name":"Promise","type":"Promise&#60;void&#62;","optional":false,"defaultValue":"","description":"Resolves when the file is deleted successfully.","expandable":false,"children":[]}]} />
### getUploadStreamDescriptor
This method is used to retrieve a write stream to be used to upload a file.
#### Example
```ts
// ...
import { Stream } from "stream"
class LocalFileService extends AbstractFileService {
// ...
async getUploadStreamDescriptor({
name,
ext,
isPrivate = true,
}: UploadStreamDescriptorType
): Promise<FileServiceGetUploadStreamResult> {
const filePath = `${isPrivate ?
this.publicPath : this.protectedPath
}/${name}.${ext}`
const pass = new Stream.PassThrough()
const writeStream = fs.createWriteStream(filePath)
pass.pipe(writeStream)
return {
writeStream: pass,
promise: Promise.resolve(),
url: `${this.serverUrl}/${filePath}`,
fileKey: filePath,
}
}
// ...
}
```
#### Parameters
<ParameterTypes parameters={[{"name":"fileData","type":"[UploadStreamDescriptorType](../../medusa/interfaces/medusa.UploadStreamDescriptorType.mdx)","description":"The details of the file being uploaded.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"name","type":"`string`","description":"The name of the file.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"ext","type":"`string`","description":"The extension of the file.","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"isPrivate","type":"`boolean`","description":"Whether the file should be uploaded to a private bucket or location. By convention, the default value of this property is `true`.","optional":true,"defaultValue":"","expandable":false,"children":[]}]}]} />
#### Returns
<ParameterTypes parameters={[{"name":"Promise","type":"Promise&#60;[FileServiceGetUploadStreamResult](../../medusa/interfaces/medusa.FileServiceGetUploadStreamResult.mdx)&#62;","optional":false,"defaultValue":"","description":"The result of the file-stream upload.","expandable":false,"children":[{"name":"FileServiceGetUploadStreamResult","type":"[FileServiceGetUploadStreamResult](../../medusa/interfaces/medusa.FileServiceGetUploadStreamResult.mdx)","optional":false,"defaultValue":"","description":"The relevant details to upload a file through a stream.","expandable":false,"children":[{"name":"writeStream","type":"`PassThrough`","description":"A [PassThrough](https://nodejs.org/api/stream.html#class-streampassthrough) write stream object to be used to write the file.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"promise","type":"Promise&#60;any&#62;","description":"A promise that should resolved when the writing process is done to finish the upload.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"url","type":"`string`","description":"The URL of the file once its uploaded.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"fileKey","type":"`string`","description":"The identifier of the file in the storage. For example, for a local file service, this can be the file's name.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]}]} />
### getDownloadStream
This method is used to retrieve a read stream for a file, which can then be used to download the file.
#### Example
```ts
class LocalFileService extends AbstractFileService {
async getDownloadStream({
fileKey,
isPrivate = true,
}: GetUploadedFileType
): Promise<NodeJS.ReadableStream> {
const filePath = `${isPrivate ?
this.publicPath : this.protectedPath
}/${fileKey}`
const readStream = fs.createReadStream(filePath)
return readStream
}
// ...
}
```
#### Parameters
<ParameterTypes parameters={[{"name":"fileData","type":"[GetUploadedFileType](../../medusa/interfaces/medusa.GetUploadedFileType.mdx)","description":"The details of the file.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"`string`","description":"The file's key.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"isPrivate","type":"`boolean`","description":"Whether the file is private.","optional":true,"defaultValue":"","expandable":false,"children":[]}]}]} />
#### Returns
<ParameterTypes parameters={[{"name":"Promise","type":"Promise&#60;ReadableStream&#62;","optional":false,"defaultValue":"","description":"The [read stream](https://nodejs.org/api/webstreams.html#class-readablestream) to read and download the file.","expandable":false,"children":[{"name":"ReadableStream","type":"`ReadableStream`","optional":false,"defaultValue":"","description":"","expandable":false,"children":[]}]}]} />
### getPresignedDownloadUrl
This method is used to retrieve a download URL of the file. For some file services, such as S3, a presigned URL indicates a temporary URL to get access to a file.
If your file service doesnt perform or offer a similar functionality, you can just return the URL to download the file.
#### Example
```ts
class LocalFileService extends AbstractFileService {
async getPresignedDownloadUrl({
fileKey,
isPrivate = true,
}: GetUploadedFileType
): Promise<string> {
// Local upload doesn't provide
// support for presigned URLs,
// so just return the file's URL.
const filePath = `${isPrivate ?
this.publicPath : this.protectedPath
}/${fileKey}`
return `${this.serverUrl}/${filePath}`
}
// ...
}
```
#### Parameters
<ParameterTypes parameters={[{"name":"fileData","type":"[GetUploadedFileType](../../medusa/interfaces/medusa.GetUploadedFileType.mdx)","description":"The details of the file.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"`string`","description":"The file's key.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"isPrivate","type":"`boolean`","description":"Whether the file is private.","optional":true,"defaultValue":"","expandable":false,"children":[]}]}]} />
#### Returns
<ParameterTypes parameters={[{"name":"Promise","type":"Promise&#60;string&#62;","optional":false,"defaultValue":"","description":"The presigned URL to download the file","expandable":false,"children":[{"name":"string","type":"`string`","optional":false,"defaultValue":"","description":"","expandable":false,"children":[]}]}]} />
---
## Test Implementation
:::note
If you created your file service in a plugin, refer to [this guide on how to test plugins](https://docs.medusajs.com/development/plugins/create#test-your-plugin).
:::
After finishing your file service implementation:
1\. Run the `build` command in the root of your Medusa backend:
```bash npm2yarn
npm run build
```
2\. Start the backend with the `develop` command:
```bash
npx medusa develop
```
3\. Upload a file using the [Admin REST APIs](https://docs.medusajs.com/api/admin#uploads_postuploads) or using the Medusa admin, for example, to [upload a product's thumbnail](https://docs.medusajs.com/user-guide/products/manage#manage-thumbnails).