Uses MongoDB's GridFS to manage binary contents of your loopback application.
-
Files are organized in containers, files and versions
-
Use loopback's where filters to filter multiple GET results
-
Option to download file in inline mode.
-
Uses the newest GridFSBucket interface, instead of the deprecated GridStore interface
-
All errors are written in English
-
When a deleting a file, all respective file chunks are deleted (no trash records left in Database)
The storage component organizes content in containers, files and versions. A container holds a collection of files, and a file can have multiple versions.
- A
container
groups files, similar to a directory or folder. However, a container cannot have child containers. A container is defined in themetadata.container
property of the file schema. - A
file
inside a container, is identified by its filename. A file's name is defined in thefilename
property of the file schema. - A
version
of a file is identified by the file's id. A file with the same filename can have multiple versions. By default, files with the same name will not overwrite each other, but instead will be stored in different versions. When replacing a file however, all previous versions of the file are destroyed and only the new version remains. A file's version is defined in the_id
property of the file schema.
Add the @patrickdk77/loopback-mongo-gridfs dependency to your project using yarn or npm.
npm install --save @patrickdk77/loopback-mongo-gridfs
or
yarn add @patrickdk77/loopback-mongo-gridfs
Add datasource to your datasources.json (or a js file as documented here)
{
"gridfs": {
"name": "gridfs",
"connector": "@patrickdk77/loopback-mongo-gridfs",
"host": "hostname",
"port": 27017,
"database": "database",
"username": "username",
"password": "password"
}
}
username and password are optional.
Alternatively, you can directly specify the MongoDB connection string by adding url
property.
❕ NOTE: Specifying the
url
property will override the connection options above.
{
"gridfs": {
"name": "gridfs",
"connector": "@patrickdk77/loopback-mongo-gridfs",
"url": "mongodb://{username}:{password}@{host}:{port}/{database}?{param1}={value1}"
}
}
❗ You can also add any extra options you may need to pass to the MongoDB client
Add a model definition file as described on loopback's documentation.
❕ There is no need to specify any properties as they will be ignored.
Example:
{
"name": "FileSystem",
"description": "File storage",
"plural": "FileSystem",
"base": "Model",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
Attach the model to the datasource as specified in loopback's documentation. In server/model-config.json
add:
{
"FileSystem": {
"dataSource": "gridfs",
"public": true
}
}
After installation, you will have access to a number of API endpoints and Javascript functions.
GET /Model/containers
Model.getContainers();
None
PATCH /FileSystem/containers/{container}
Model.renameContainer(container, newName);
-
container
: (Required) Container name -
newName
: (Required) New container name
❗ Renaming the container to an existing container name will result to merging its contents to the latter. If a filename already exist, it will be added as a version in the file's version list.
DELETE /FileSystem/containers/{container}
Model.deleteContainer(container);
-
container
: (Required) Container name
GET /FileSystem/containers/{container}/files
Model.getContainerFiles(container, where);
-
container
: (Required) Container name -
where
: (Optional) Where filter (see Loopback's documentation for more info)
PUT /FileSystem/containers/{container}/files
Model.replaceContainerFiles(container, req);
-
container
: (Required) Container name -
req
: (Required) The request object
POST /FileSystem/containers/{container}/files
Model.uploadContainerFiles(container, req);
-
container
: (Required) Container name -
req
: (Required) The request object
❕ You can also add any custom metadata you may need to store with each file (see Uploading files for more info)
GET /FileSystem/containers/{container}/files/count
Model.countContainerFiles(container, where);
-
container
: (Required) Container name -
where
: (Optional) Where filter (see Loopback's documentation for more info)
GET /FileSystem/containers/{container}/files/download
Model.downloadContainerFiles(container, where, res);
-
container
: (Required) Container name -
where
: (Optional) Where filter (see Loopback's documentation for more info) -
res
: (Required) The response object
GET /FileSystem/containers/{container}/files/downloadOne
Model.downloadContainerFileWhere(container, where, alias, inline, res);
-
container
: (Required) Container name -
where
: (Optional) Where filter (see Loopback's documentation for more info) -
alias
: (Optional) Use this to download the file with a different filename than the original. Default is{$filename}
(see alias for more info) -
inline
: (Optional) Boolean indicating whether to download inline (true
) or as an attachment (false
). Default isfalse
-
res
: (Required) The response object
GET /FileSystem/containers/{container}/files/{file}
Model.getContainerFile(container, file);
-
container
: (Required) Container name -
file
: (Required) File name
DELETE /FileSystem/containers/{container}/files/{file}
Model.deleteContainerFile(container, file);
-
container
: (Required) Container name -
file
: (Required) File name
GET /FileSystem/containers/{container}/files/{file}/download
Model.downloadContainerFile(container, file, res, alias, inline);
-
container
: (Required) Container name -
file
: (Required) File name -
res
: (Required) The response object -
alias
: (Optional) Use this to download the file with a different filename than the original. Default is{$filename}
(see alias for more info) -
inline
: (Optional) Boolean indicating whether to download inline (true
) or as an attachment (false
). Default isfalse
GET /FileSystem/containers/{container}/files/{file}/versions
Model.getFileVersions(container, file, where);
-
container
: (Required) Container name -
file
: (Required) File name -
where
: (Optional) Where filter (see Loopback's documentation for more info)
GET /FileSystem/containers/{container}/files/{file}/versions/count
Model.countFileVersions(container, file, where);
-
container
: (Required) Container name -
file
: (Required) File name -
where
: (Optional) Where filter (see Loopback's documentation for more info)
GET /FileSystem/containers/{container}/files/{file}/versions/download
Model.downloadFileVersions(container, file, alias, where, res);
-
container
: (Required) Container name -
file
: (Required) File name -
alias
: (Optional) Use this to download the file with a different filename than the original (always starts with{$_id}_
to separate each version). Default is{$filename}
which results to{$_id}_{$filename}
(see alias for more info). -
where
: (Optional) Where filter (see Loopback's documentation for more info) -
res
: (Required) The response object
GET /FileSystem/containers/{container}/files/{file}/versions/{version}
Model.getFileVersion(container, file, version);
-
container
: (Required) Container name -
file
: (Required) File name -
version
: (Required) Version ID
PATCH /FileSystem/containers/{container}/files/{file}/versions/{version}
Model.updateFileVersion(container, file, version, metadata);
-
container
: (Required) Container name -
file
: (Required) File name -
version
: (Required) Version ID -
metadata
: (Required) metadata items to change
DELETE /FileSystem/containers/{container}/files/{file}/versions/{version}
Model.deleteFileVersion(container, file, version);
-
container
: (Required) Container name -
file
: (Required) File name -
version
: (Required) Version ID
GET /FileSystem/containers/{container}/files/{file}/versions/{version}/download
Model.downloadFileVersion(container, file, version, res, alias, inline);
-
container
: (Required) Container name -
file
: (Required) File name -
version
: (Required) Version ID -
res
: (Required) The response object -
alias
: (Optional) Use this to download the file with a different filename than the original. Default is{$_id}_{$filename}
(see alias for more info) -
inline
: (Optional) Boolean indicating whether to download inline (true
) or as an attachment (false
). Default isfalse
None
Model.find(where);
-
where
: (Required) Where filter (see Loopback's documentation for more info)
None
Model.findOne(where);
-
where
: (Required) Where filter (see Loopback's documentation for more info)
None
Model.fileUpload(container, fileStream, filename, mimetype, customMetadata);
-
container
: (Required) Container name -
fileStream
: (Required) The file's stream -
filename
: (Required) File name -
mimetype
: (Required) The file's mimetype -
customMetadata
: (Optional) Metadata object to be attached to the file
None
Model.delete(where, countFiles);
-
where
: (Required) Where filter (see Loopback's documentation for more info) -
countFiles
: (Optional) Boolean indicating whether to return a counter for deleted files (true
). Default isfalse
.
None
Model.deleteById(versionIds);
-
versionIds
: (Required) Array of ids to delete.
None
Model.countFiles(where);
-
where
: (Required) Where filter (see Loopback's documentation for more info)
None
Model.countVersions(where);
-
where
: (Required) Where filter (see Loopback's documentation for more info)
You can upload single or multiple files in a container by POSTing the files as form data, as seen in the example bellow:
curl --location --request POST 'http://localhost:3000/api/FileSystem/containers/temp/files?access_token=<YOUR_ACCESS_TOKEN>'
--form '=@"/path/to/file/sample1.txt"'
--form '=@"/path/to/file/sample2.pdf"'
The response you would receive upon a successful request would be an array containing GridFS's metadata for each file:
[
{
"_id": "60097682cca2d7230c2ab698",
"length": 652007,
"chunkSize": 261120,
"uploadDate": "2021-01-21T12:41:38.806Z",
"filename": "sample1.txt",
"md5": "545b509b37c726dc3d30476806eb5937",
"metadata": {
"container": "temp",
"mimetype": "text/plain",
"extension": "txt"
}
},
{
"_id": "60097682cca2d7230c2ab699",
"length": 9689,
"chunkSize": 261120,
"uploadDate": "2021-01-21T12:41:38.787Z",
"filename": "sample2.pdf",
"md5": "e572fdbb0a1ce8d3b77c5f4e59db82fe",
"metadata": {
"container": "temp",
"mimetype": "application/pdf",
"extension": "pdf"
}
}
]
❗ You can then filter results based on these properties, when listing or downloading files from the respective API endpoints (see Where filter in Loopback's official documentation for more info).
You may also specify any custom metadata you want to store along with the file's default metadata. In order to do that you first need to specify keys for each of your file inputs. Then you can specify your metadata by adding text inputs with keys formatted like this "<CORRESPONDING_FILE_KEY>_meta"
and values being the stringified version of a JSON object containing your custom metadata. Here is an example of uploading files with custom metadata:
curl --location --request POST 'http://localhost:3000/api/FileSystem/containers/temp/files?access_token=<YOUR_ACCESS_TOKEN>'
--form 'file0=@"/path/to/file/sample1.txt"'
--form 'file0_meta="{ "createdBy": "user1", "someOtherMeta": "test" }"'
--form 'file1=@"/path/to/file/sample3.png"'
--form 'file1_meta="{ "createdBy": "user2"}"'
You will then see these properties added to the metadata object in each result:
[
{
"_id": "6009794bcca2d7230c2ab69e",
"length": 652007,
"chunkSize": 261120,
"uploadDate": "2021-01-21T12:53:32.209Z",
"filename": "sample1.txt",
"md5": "545b509b37c726dc3d30476806eb5937",
"metadata": {
"createdBy": "user1",
"someOtherMeta": "test",
"container": "temp",
"mimetype": "text/plain",
"extension": "txt"
}
},
{
"_id": "6009794bcca2d7230c2ab69f",
"length": 9689,
"chunkSize": 261120,
"uploadDate": "2021-01-21T12:53:32.190Z",
"filename": "sample2.pdf",
"md5": "e572fdbb0a1ce8d3b77c5f4e59db82fe",
"metadata": {
"createdBy": "user2",
"container": "temp",
"mimetype": "application/pdf",
"extension": "pdf"
}
}
]
Many endpoints / functions used for downloading files, have amongst others the optional alias
argument available. You can use this argument to specify the name of the file to be downloaded in case you don't want to download with the filename it is stored with.
For example, this GET call:
http://localhost:3000/api/FileSystem/containers/temp/files/sample1.txt/download?alias=renamed.txt
Will download the file sample1.txt
as renamed.txt
You can also specify patterns instead of static text that will allow you to use values from the file's metadata to compose the download name. You can do that by inputting string values which contain the path to the metadata value enclosed in {$...}.
For example, suppose we have the following file stored in the DB:
[
{
"_id": "60097f0ecca2d7230c2ab6a4",
"length": 652007,
"chunkSize": 261120,
"uploadDate": "2021-01-21T13:18:06.755Z",
"filename": "sample1.txt",
"md5": "545b509b37c726dc3d30476806eb5937",
"metadata": {
"customMeta": "test",
"container": "temp",
"mimetype": "text/plain",
"extension": "txt"
}
}
]
Now executing this GET call:
http://localhost:3000/api/FileSystem/containers/temp/files/sample1.txt/download?alias={$id}{$metadata.customMeta}.{$metadata.extension}
Would result in this download name: 60097f7bcca2d7230c2ab6a8_test.txt
(1.x.x) to (2.x.x): Completely rewritten the module with new endpoints & functionality
Copyright (c) Marios Vertopoulos & Vassilis Katsaris