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.
I had the same problem. I solved it by creating my own models to store meta data and my own upload methods.
I created a model File
which will store info like name,type,url,userId ( same as yours)
I created my own upload remote method because I was unable to do it with the hooks. Container model is the model which is created by loopback-component-storage.
var fileInfo = fileObj.files.myFile[0];
Here myFile is the fieldname for file upload, so you will have to change it accordingly. If you don't specify any field, then it will come as fileObj.file.null[0]
. This code lacks proper error checking, do it before deploying it in production.
File.uploadFile = function (ctx,options,cb) { File.app.models.container.upload(ctx.req,ctx.result,options,function (err,fileObj) { if(err) cb(err); else{ // Here myFile is the field name associated with upload. You should change it to something else if you var fileInfo = fileObj.files.myFile[0]; File.create({ name: fileInfo.name, type: fileInfo.type, container: fileInfo.container, userId: ctx.req.accessToken.userId, url: CONTAINERS_URL+fileInfo.container+'/download/'+fileInfo.name // This is a hack for creating links },function (err,obj) { if(err){ console.log('Error in uploading' + err); cb(err); } else{ cb(null,obj); } }); } }); }; File.remoteMethod( 'uploadFile', { 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'} } );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With