--- outline: deep --- # File uploads in FeathersJS Over the last months we at [ciancoders.com](https://ciancoders.com/) have been working in a new SPA project using Feathers and React, the combination of those two turns out to be **just amazing**. Recently we were struggling to find a way to upload files without having to write a separate Express middleware or having to (re)write a complex Feathers service. ## Our Goals We want to implement an upload service to accomplish a few important things: 1. It has to handle large files (+10MB). 2. It needs to work with the app's authentication and authorization. 3. The files need to be validated. 4. At the moment there is no third party storage service involved, but this will change in the near future, so it has to be prepared. 5. It has to show the upload progress. The plan is to upload the files to a feathers service so we can take advantage of hooks for authentication, authorization and validation, and for service events. Fortunately, there exists a file storage service: [feathers-blob](https://github.com/feathersjs/feathers-blob). With it we can meet our goals, but (spoiler alert) it isn't an ideal solution. We discuss some of its problems below. ## Basic upload with feathers-blob and feathers-client For the sake of simplicity, we will be working over a very basic feathers server, with just the upload service. Lets look at the server code: ```javascript /* --- server.js --- */ const feathers = require('@feathersjs/feathers'); const express = require('@feathersjs/express'); const socketio = require('@feathersjs/socketio'); // feathers-blob service const blobService = require('feathers-blob'); // Here we initialize a FileSystem storage, // but you can use feathers-blob with any other // storage service like AWS or Google Drive. const fs = require('fs-blob-store'); const blobStorage = fs(__dirname + '/uploads'); // Feathers app const app = express(feathers()); // Parse HTTP JSON bodies app.use(express.json()); // Parse URL-encoded params app.use(express.urlencoded({ extended: true })); // Add REST API support app.configure(express.rest()); // Configure Socket.io real-time APIs app.configure(socketio()); // Upload Service app.use('/uploads', blobService({Model: blobStorage})); // Register a nicer error handler than the default Express one app.use(express.errorHandler()); // Start the server app.listen(3030, function(){ console.log('Feathers app started at localhost:3030') }); ``` Let's look at this implemented in the `@feathersjs/cli` generated server code: ```javascript /* --- /src/services/uploads/uploads.service.js --- */ // Initializes the `uploads` service on path `/uploads' const createModel = require('../../models/uploads.model'); const hooks = require('./uploads.hooks'); const filters = require('./uploads.filters'); // feathers-blob service const blobService = require('feathers-blob'); // Here we initialize a FileSystem storage, // but you can use feathers-blob with any other // storage service like AWS or Google Drive. const fs = require('fs-blob-store'); // File storage location. Folder must be created before upload. // Example: './uploads' will be located under feathers app top level. const blobStorage = fs('./uploads'); module.exports = function() { const app = this; const Model = createModel(app); const paginate = app.get('paginate'); // Initialize our service with any options it requires app.use('/uploads', blobService({ Model: blobStorage})); // Get our initialized service so that we can register hooks and filters const service = app.service('uploads'); service.hooks(hooks); if (service.filter) { service.filter(filters); } }; ``` `feathers-blob` works over abstract-blob-store, which is an abstract interface to various storage backends, such as filesystem, AWS, or Google Drive. It only accepts and retrieves files encoded as dataURI strings. Just like that we have our backend ready, go ahead and POST something to localhost:3030/uploads`, for example with postman: ```json { 'uri': 'data:image/gif;base64,R0lGODlhEwATAPcAAP/+//7/////+////fvzYvryYvvzZ/fxg/zxWfvxW/zwXPrtW/vxXvfrXv3xYvrvYvntYvnvY/ruZPrwZPfsZPjsZfjtZvfsZvHmY/zxavftaPrvavjuafzxbfnua/jta/ftbP3yb/zzcPvwb/zzcfvxcfzxc/3zdf3zdv70efvwd/rwd/vwefftd/3yfPvxfP70f/zzfvnwffvzf/rxf/rxgPjvgPjvgfnwhPvzhvjvhv71jfz0kPrykvz0mv72nvblTPnnUPjoUPrpUvnnUfnpUvXlUfnpU/npVPnqVPfnU/3uVvvsWPfpVvnqWfrrXPLiW/nrX/vtYv7xavrta/Hlcvnuf/Pphvbsif3zk/zzlPzylfjuk/z0o/LqnvbhSPbhSfjiS/jlS/jjTPfhTfjlTubUU+/iiPPokfrvl/Dll/ftovLWPfHXPvHZP/PbQ/bcRuDJP/PaRvjgSffdSe3ddu7fge7fi+zkuO7NMvPTOt2/Nu7SO+3OO/PWQdnGbOneqeneqvDqyu3JMuvJMu7KNfHNON7GZdnEbejanObXnOW8JOa9KOvCLOnBK9+4Ku3FL9ayKuzEMcenK9e+XODOiePSkODOkOW3ItisI9yxL+a9NtGiHr+VH5h5JsSfNM2bGN6rMJt4JMOYL5h4JZl5Jph3Jpl4J5h5J5h3KJl4KZp5Ks+sUN7Gi96lLL+PKMmbMZt2Jpp3Jpt3KZl4K7qFFdyiKdufKsedRdm7feOpQN2QKMKENrpvJbFfIrNjJL1mLMBpLr9oLrFhK69bJFkpE1kpFYNeTqFEIlsoFbmlnlsmFFwpGFkoF/////7+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAANAALAAAAAATABMAAAj/AKEJHCgokKJKlhThGciQYSIva7r8SHPFzqGGAwPd4bKlh5YsPKy0qFLnT0NAaHTcsIHDho0aKkaAwGCGEkM1NmSkIjWLBosVJT6cOjUrzsBKPl54KmYsACoTMmk1WwaA1CRoeM7siJEqmTIAsjp40ICK2bEApfZcsoQlxwxRzgI8W8XhgoVYA+Kq6sMK0QEYKVCUkoVqQwQJFTwFEAAAFZ9PlFy4OEEiRIYJD55EodDA1ClTbPp0okRFxBQDBRgskAKhiRMlc+Sw4SNpFCIoBBwkUMBkCBIiY8qAgcPG0KBHrBTFQbCEV5EjQYQACfNFjp5CgxpxagVtUhIjwzaJYSHzhQ4cP3ryQHLEqJbASnu+6EIW6o2b2X0ISXK0CFSugazs0YYmwQhziyuE2PLLIv3h0hArkRhiCCzAENOLL7tgAoqDGLXSSSaPMLIIJpmAUst/GA3UCiuv1PIKLtw1FBAAOw==' } ``` The service will respond with something like this: ```json { 'id': '6454364d8facd7a88e627e4c4b11b032d2f83af8f7f9329ffc2b7a5c879dc838.gif', 'uri': 'the-same-uri-we-uploaded', 'size': 1156 } ``` Or we can implement a very basic frontend with `feathers-client` and `jQuery`: ```html