What I want to do: Have an html form, with a file input inside. When a file is chosen, the file input should upload the file, and get a file id, so when the form is submitted, the file id is posted with the form and written in the database.
Shorter version: I want to store meta data (id for example) with my files.
Sounds simple, yet I struggle to do that in LoopBack.
There has been a couple conversations ( 1, 2 ) about this topic, and neither seemed to lead to a solution, so I thought this might be a good place to find one once and for all.
The simplest solution would be to use model relations, but LoopBack doesn't support relations with the file storage service. Bump. So we have to go with a persistedmodel named File
for example, and override default create, delete so it saves and deletes from the file store model I have - named Storage
.
My setup so far:
name
,size
, url
and objectId
create
so the file can be saved first and then it's url
can be injected into File.create()
I'm there, and according to this LoopBack page, I have the ctx which should have the file inside:
File.beforeRemote('create', function(ctx, affectedModelInstance, next) {})`
What's ctx
?
ctx.req
: Express Request object.
ctx.result
: Express Response object.
Ok, so now I'm at the Express page, pretty lost, and it sais something about a 'body-parsing middleware' which I have no idea what it might be.
I feel like I'm close to the solution, any help would be appreciated. Is this approach right?
Here's the full solution for storing meta data with files in loopback.
You need a container model
common/models/container.json
{
"name": "container",
"base": "Model",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}
Create the data source for your container in server/datasources.json
. For example:
...
"storage": {
"name": "storage",
"connector": "loopback-component-storage",
"provider": "filesystem",
"root": "/var/www/storage",
"maxFileSize": "52428800"
}
...
You'll need to set the data source of this model in server/model-config.json
to the loopback-component-storage
you have:
...
"container": {
"dataSource": "storage",
"public": true
}
...
You'll also need a file model to store the meta data and handle container calls:
common/models/files.json
{
"name": "files",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"url": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}
And now connect files
with container
:
common/models/files.js
var CONTAINERS_URL = '/api/containers/';
module.exports = function(Files) {
Files.upload = function (ctx,options,cb) {
if(!options) options = {};
ctx.req.params.container = 'common';
Files.app.models.container.upload(ctx.req,ctx.result,options,function (err,fileObj) {
if(err) {
cb(err);
} else {
var fileInfo = fileObj.files.file[0];
Files.create({
name: fileInfo.name,
type: fileInfo.type,
container: fileInfo.container,
url: CONTAINERS_URL+fileInfo.container+'/download/'+fileInfo.name
},function (err,obj) {
if (err !== null) {
cb(err);
} else {
cb(null, obj);
}
});
}
});
};
Files.remoteMethod(
'upload',
{
description: 'Uploads a file',
accepts: [
{ arg: 'ctx', type: 'object', http: { source:'context' } },
{ arg: 'options', type: 'object', http:{ source: 'query'} }
],
returns: {
arg: 'fileObject', type: 'object', root: true
},
http: {verb: 'post'}
}
);
};
For expose the files api add to the model-config.json
file the files
model, remember select your correct datasources:
...
"files": {
"dataSource": "db",
"public": true
}
...
Done! You can now call POST /api/files/upload
with a file binary data in file
form field. You'll get back id, name, type, and url in return.