Skip to main content

File Storage

Implementing File Upload

You can create a field for file uploads in any EzModel using the type Type.FILE.

For example, if we want to store a user's profile picture in an EzModel called UserDetails:

const model = new EzModel('UserDetails', {
.
.
profilePicture: Type.FILE
});

Uploading/Updating a File

Data Specification

You can upload files using the multipart/form-data content-type using the automatically generated CREATE and UPDATE endpoints.

The data must be sent to EzBackend in the following format

PropertyFormat (CREATE)Format (UPDATE)
URL{backend-url}/{ez-model-prefix}/multipart{backend-url}/{ez-model-prefix}/{model-id}/multipart
URL Examplehttp://localhost:8000/user-details/multiparthttp://localhost:8000/user-details/123456789/multipart
MethodPOSTPATCH
Content Typemultipart/form-datamultipart/form-data
DataFormDataFormData
info

Note that the mimetype that your data is uploaded with will be reflected when the file is downloaded

Currently there is no configurable method to ensure the mimetype of the file uploaded. For example a malicious user can upload a video instead of an image. However, you can view protecting routes to implement your own custom mimetype checking functionality

Required Properties

All properties in the EzModel are required by default, which means that the CREATE application/json will not work if there is a Type.FILE.

By making the Type.FILE optional, we can allow initial data upload using application/json and remaining file with multipart/form-data

const model = new EzModel('UserDetails', {
.
.
profilePicture: {
type: Type.FILE,
nullable: true
}
});
caution

Support for Base64 encoding to support application/json may be included in the future, but is not recommended because of the additional overhead due to CPU and memory usage on both the server and client.

Upload Examples

Testing in OpenAPI

React.ts / React.js

If you have a form with fields for the user's name, age and profile picture

<form onSubmit="{handleSubmit}">
<input name="name" type="text" placeholder="Name" /><br />
<input name="age" type="text" placeholder="Age" /><br />
<input name="profilePicture" type="file" /><br />
<input type="submit" value="Submit" />
</form>

You can directly obtain the data in the element and use it in a fetch request for uploading your file.

const formElement = e.target as HTMLFormElement

const formData = new FormData(formElement)
const result = await fetch('http://localhost:8000/user-details/multipart',{
method: 'POST',
body: formData
})
caution

By default submitted values will be type-casted. This is because multipart/form-data only supports text and file types.

See Type Casting

JavaScript Fetch

You can manually set your own data to submit with a form. Note that the data appended to the form data is of type string despite being numerical values, because multipart/form-data only supports text and file types

const formData = new FormData()

formData.append('name','Robert')
formData.append('age','22')
formData.append('profilePicture',new Blob(['This is a placeholder for the profile Picture']))

const result = await fetch('http://localhost:8000/user-details/multipart',{
method: 'POST',
body: formData
})

const resultJSON = await result.json()
caution

By default submitted values will be type-casted. This is because multipart/form-data only supports text and file types.

See Type Casting

Downloading Files

You can download files by specifying the prefix of the EzModel you wish to download from, as well as the id of the EzModel

Data Specification

PropertyFormat (DOWNLOAD)
URL{backend-url}/{ez-model-prefix}/{id}/file/{propertyName}
URL Examplehttp://localhost:8000/user-details/1/file/profilePicture
MethodGET
Content TypeThe same as the mimetype the file was uploaded with
Content Dispositionattachment; filename="{originalFileName}"

It is important to take note that the default content disposition is as an attachment, which means that when the URL is opened manually the browser will attempt to download the file as the default behaviour.

Download Examples

As a Rendered File

Since the mimetype is reflected, you can provide the download URL in order to serve any image or video files.

<img src="http://localhost:8000/user-details/1/file/profilePicture" />

As a File Download

You can allow a user to download a file by providing the download URL in a <a> tag

<a href="http://localhost:8000/user-details/1/file/profilePicture">Download</a>

Type Casting

By default, since multipart/form-data can only send file data and text data, EzBackend will automatically coerce types in the backend depending on what is specified in the EzModel

Possible type coercions:

from type 
to type 
string
string-
number /
integer
Valid number /
integer: x+x
boolean"false"false
"true"true
"abc"
""

Coercion from string values

To number type

Coercion to number is possible if the string is a valid number, +data is used.

To integer type

Coercion to integer is possible if the string is a valid number without fractional part (data % 1 === 0).

To boolean type

Unlike JavaScript, only these strings can be coerced to boolean:

  • "true" -> true
  • "false" -> false

AJV

Under the hood, type coercion and validation is done with AJV. You can view their specification for additional details.

Configuration

File Storage can be configured globally or at the router level.

There are two components that can be configured, the engine and multipartOpts

The order of preference in which the configuration is merged is

  1. router setting
  2. global setting
  3. default setting

e.g If the configuration is defined both at the global level and router level, the router level configuration will take precedence.

Global Configuration

The global setting can be set in app.start(). See Configuration for more details

Router Level Configuration

You can configure the options for the generated routes in EzModel by using the routerOpts

const sampleModel = new EzModel(
'SampleModel',
{
avatar: Type.FILE,
},
{
routerOpts: {
storage: {
engine: customEngine,
multipartOpts: {
limits: {
fileSize: 1024,
},
},
},
},
},
);

Default Configuration

By default EzBackend uses the diskEngine, which can be customised to your needs and pass to the global configuration or the router level configuration.

How it works

For a property with Type.FILE is specified in an EzModel

  1. The corresponding field in the EzRepo is a JSON field containing metadata on any files uploaded
  2. Custom CREATE, READ and UPDATE multipart/form-data endpoints are available for the model to edit the file. (The application/json DELETE endpoint is reused for deleting the file)

Under the hood, EzBackend uses fastify-multipart to handle file uploads.

As for engines, it uses multer compatible storage engines in order to provide file upload and file deleting functionality.

However, EzBackend requires an additional custom file download functionality in order to serve files to the end user.

Caveats

Since all file uploads and downloads are streamed through EzBackend, this results in additional bandwidth used when using engines such as AWS S3.

For example, the data travels through the path

AWS -> EzBackend -> User

If this becomes a concern, you can reduce bandwidth usage by generating presigned URLs with your storage engine in order to allow users to directly download large files from your storage engine.