I know this has been asked many times before, and I have read almost all I could find about the subject, namely:
https://stackoverflow.com/a/25022437/1031184
Uploading images using Node.js, Express, and Mongoose
Those are the best I have found so far. My problem is tho that they still aren't very clear, there is very little documentation online at all about this and the discussion seems aimed at people who are much more advanced than I am.
So with that I would really love it if someone could please walk me though how to upload images using Mongoose, Express & AngularJS. I am actually using the MEAN fullstack. (this generator to be precise – https://github.com/DaftMonk/generator-angular-fullstack)
AddController:
'use strict';
angular.module('lumicaApp')
.controller('ProjectAddCtrl', ['$scope', '$location', '$log', 'projectsModel', 'users', 'types', function ($scope, $location, $log, projectsModel, users, types) {
$scope.dismiss = function () {
$scope.$dismiss();
};
$scope.users = users;
$scope.types = types;
$scope.project = {
name: null,
type: null,
images: {
thumbnail: null // I want to add the uploaded images _id here to reference with mongoose populate.
},
users: null
};
$scope.save = function () {
$log.info($scope.project);
projectsModel.post($scope.project).then(function (project) {
$scope.$dismiss();
});
}
}]);
I want to add the Images ID reference to project.images.thumbnail but I want to store all the information inside an Image Object using the following Schema:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ImageSchema = new Schema({
fileName: String,
url: String,
contentType: String,
size: String,
dimensions: String
});
module.exports = mongoose.model('Image', ImageSchema);
I have also added the following https://github.com/nervgh/angular-file-upload to my bower packages.
As I say I just can't figure out how to tie it all together. And I'm not even sure if what I am trying to do is the correct way either.
--------------------------------------------------------------------------\
UPDATE:
Here is what I now have, I have added some comments detailing how I would like it to work, unfortunately I still haven't managed to get this working, I can't even get the image to start uploading, never mind uploading to S3. Sorry to be a pain but I am just finding this particularly confusing, which surprises me.
client/app/people/add/add.controller.js
'use strict';
angular.module('lumicaApp')
.controller('AddPersonCtrl', ['$scope', '$http', '$location', '$window', '$log', 'Auth', 'FileUploader', 'projects', 'usersModel', function ($scope, $http, $location, $window, $log, Auth, FileUploader, projects, usersModel) {
$scope.dismiss = function () {
$scope.$dismiss();
};
$scope.newResource = {};
// Upload Profile Image
$scope.onUploadSelect = function($files) {
$scope.newResource.newUploadName = $files[0].name;
$http
.post('/api/uploads', {
uploadName: newResource.newUploadName,
upload: newResource.newUpload
})
.success(function(data) {
newResource.upload = data; // To be saved later
});
};
$log.info($scope.newResource);
//Get Projects List
$scope.projects = projects;
//Register New User
$scope.user = {};
$scope.errors = {};
$scope.register = function(form) {
$scope.submitted = true;
if(form.$valid) {
Auth.createUser({
firstName: $scope.user.firstName,
lastName: $scope.user.lastName,
username: $scope.user.username,
profileImage: $scope.user.profileImage, // I want to add the _id reference for the image here to I can populate it with 'ImageSchema' using mongoose to get the image details(Name, URL, FileSize, ContentType, ETC)
assigned: {
teams: null,
projects: $scope.user.assigned.projects
},
email: $scope.user.email,
password: $scope.user.password
})
.then( function() {
// Account created, redirect to home
//$location.path('/');
$scope.$dismiss();
})
.catch( function(err) {
err = err.data;
$scope.errors = {};
// Update validity of form fields that match the mongoose errors
angular.forEach(err.errors, function(error, field) {
form[field].$setValidity('mongoose', false);
$scope.errors[field] = error.message;
});
});
}
};
$scope.loginOauth = function(provider) {
$window.location.href = '/auth/' + provider;
};
}]);
server/api/image/image.model.js I would like to store all image information here and use this to populate profileImage in people controller.
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ImageSchema = new Schema({
fileName: String,
url: String, // Should store the URL of image on S3.
contentType: String,
size: String,
dimensions: String
});
module.exports = mongoose.model('Image', ImageSchema);
client/app/people/add/add.jade
.modal-header
h3.modal-title Add {{ title }}
.modal-body
form(id="add-user" name='form', ng-submit='register(form)', novalidate='')
.form-group(ng-class='{ "has-success": form.firstName.$valid && submitted,\
"has-error": form.firstName.$invalid && submitted }')
label First Name
input.form-control(type='text', name='firstName', ng-model='user.firstName', required='')
p.help-block(ng-show='form.firstName.$error.required && submitted')
| First name is required
.form-group(ng-class='{ "has-success": form.lastName.$valid && submitted,\
"has-error": form.lastName.$invalid && submitted }')
label Last Name
input.form-control(type='text', name='lastName', ng-model='user.lastName', required='')
p.help-block(ng-show='form.lastName.$error.required && submitted')
| Last name is required
.form-group(ng-class='{ "has-success": form.username.$valid && submitted,\
"has-error": form.username.$invalid && submitted }')
label Username
input.form-control(type='text', name='username', ng-model='user.username', required='')
p.help-block(ng-show='form.username.$error.required && submitted')
| Last name is required
// Upload Profile Picture Here
.form-group
label Profile Image
input(type="file" ng-file-select="onUploadSelect($files)" ng-model="newResource.newUpload")
.form-group(ng-class='{ "has-success": form.email.$valid && submitted,\
"has-error": form.email.$invalid && submitted }')
label Email
input.form-control(type='email', name='email', ng-model='user.email', required='', mongoose-error='')
p.help-block(ng-show='form.email.$error.email && submitted')
| Doesn't look like a valid email.
p.help-block(ng-show='form.email.$error.required && submitted')
| What's your email address?
p.help-block(ng-show='form.email.$error.mongoose')
| {{ errors.email }}
.form-group(ng-class='{ "has-success": form.password.$valid && submitted,\
"has-error": form.password.$invalid && submitted }')
label Password
input.form-control(type='password', name='password', ng-model='user.password', ng-minlength='3', required='', mongoose-error='')
p.help-block(ng-show='(form.password.$error.minlength || form.password.$error.required) && submitted')
| Password must be at least 3 characters.
p.help-block(ng-show='form.password.$error.mongoose')
| {{ errors.password }}
.form-group
label Assign Project(s)
br
select(multiple ng-options="project._id as project.name for project in projects" ng-model="user.assigned.projects")
button.btn.btn-primary(ng-submit='register(form)') Save
pre(ng-bind="user | json")
.modal-footer
button.btn.btn-primary(type="submit" form="add-user") Save
button.btn.btn-warning(ng-click='dismiss()') Cancel
server/api/upload/index.js
'use strict';
var express = require('express');
var controller = require('./upload.controller');
var router = express.Router();
//router.get('/', controller.index);
//router.get('/:id', controller.show);
router.post('/', controller.create);
//router.put('/:id', controller.update);
//router.patch('/:id', controller.update);
//router.delete('/:id', controller.destroy);
module.exports = router;
server/api/upload/upload.controller.js
'use strict';
var _ = require('lodash');
//var Upload = require('./upload.model');
var aws = require('aws-sdk');
var config = require('../../config/environment');
var randomString = require('../../components/randomString');
// Creates a new upload in the DB.
exports.create = function(req, res) {
var s3 = new aws.S3();
var folder = randomString.generate(20); // I guess I do this because when the user downloads the file it will have the original file name.
var matches = req.body.upload.match(/data:([A-Za-z-+\/].+);base64,(.+)/);
if (matches === null || matches.length !== 3) {
return handleError(res, 'Invalid input string');
}
var uploadBody = new Buffer(matches[2], 'base64');
var params = {
Bucket: config.aws.bucketName,
Key: folder + '/' + req.body.uploadName,
Body: uploadBody,
ACL:'public-read'
};
s3.putObject(params, function(err, data) {
if (err)
console.log(err)
else {
console.log("Successfully uploaded data to my-uploads/" + folder + '/' + req.body.uploadName);
return res.json({
name: req.body.uploadName,
bucket: config.aws.bucketName,
key: folder
});
}
});
};
function handleError(res, err) {
return res.send(500, err);
}
server/config/environment/development.js
aws: {
key: 'XXXXXXXXXXXX',
secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
region: 'sydney',
bucketName: 'my-uploads'
}
All of this code is straight out of a project that depends heavily on this for large file uploads and images. Definitely checkout https://github.com/nervgh/angular-file-upload
In my view somewhere:
<div class="form-group">
<label>File Upload</label>
<input type="file" ng-file-select="onUploadSelect($files)" ng-model="newResource.newUpload">
</div>
Using the module angularFileUpload I then have in my controller:
$scope.onUploadSelect = function($files) {
$scope.newResource.newUploadName = $files[0].name;
};
https://github.com/nervgh/angular-file-upload
When the user clicks upload this gets executed where I send the file to be uploaded:
$http
.post('/api/uploads', {
uploadName: newResource.newUploadName,
upload: newResource.newUpload
})
.success(function(data) {
newResource.upload = data; // To be saved later
});
This request is sent to a controller that looks something like this:
'use strict';
var _ = require('lodash');
var aws = require('aws-sdk');
var config = require('../../config/environment');
var randomString = require('../../components/randomString');
// Creates a new upload in the DB.
exports.create = function(req, res) {
var s3 = new aws.S3();
var folder = randomString.generate(20); // I guess I do this because when the user downloads the file it will have the original file name.
var matches = req.body.upload.match(/data:([A-Za-z-+\/].+);base64,(.+)/);
if (matches === null || matches.length !== 3) {
return handleError(res, 'Invalid input string');
}
var uploadBody = new Buffer(matches[2], 'base64');
var params = {
Bucket: config.aws.bucketName,
Key: folder + '/' + req.body.uploadName,
Body: uploadBody,
ACL:'public-read'
};
s3.putObject(params, function(err, data) {
if (err)
console.log(err)
else {
console.log("Successfully uploaded data to csk3-uploads/" + folder + '/' + req.body.uploadName);
return res.json({
name: req.body.uploadName,
bucket: config.aws.bucketName,
key: folder
});
}
});
};
function handleError(res, err) {
return res.send(500, err);
}
server/components/randomString/index.js
'use strict';
module.exports.generate = function(textLength) {
textLength = textLength || 10;
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for(var i = 0; i < textLength; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
server/config/environment/development.js
server/api/upload/upload.controller.js
This is the way i used MEAN.JS for file upload.
Model
var UserSchema = new mongoose.Schema({
name:{type:String,required:true},
photo:Buffer // Image
});
Server Controller
var userPicture = function(req,res){ // Stores Picture for a user matching the ID.
user.findById(req.param('id'), function (err, user) {
console.log(req.files) // File from Client
if(req.files.file){ // If the Image exists
var fs = require('node-fs');
fs.readFile(req.files.file.path, function (dataErr, data) {
if(data) {
user.photo ='';
user.photo = data; // Assigns the image to the path.
user.save(function (saveerr, saveuser) {
if (saveerr) {
throw saveerr;
}
res.json(HttpStatus.OK, saveuser);
});
}
});
return
}
res.json(HttpStatus.BAD_REQUEST,{error:"Error in file upload"});
});
};
Client Controller
$scope.saveuserImage = function(){
$scope.upload = $upload.upload({ // Using $upload
url: '/user/'+$stateParams.id+'/userImage', // Direct Server Call.
method:'put',
data:'', // Where the image is going to be set.
file: $scope.file
}).progress(function (evt) {})
.success(function () {
var logo = new FileReader(); // FileReader.
$scope.onAttachmentSelect = function(file){
logo.onload = function (e) {
$scope.image = e.target.result; // Assigns the image on the $scope variable.
$scope.logoName = file[0].name; // Assigns the file name.
$scope.$apply();
};
logo.readAsDataURL(file[0]);
$scope.file = file[0];
$scope.getFileData = file[0].name
};
location.reload();
$scope.file = "";
$scope.hideUpload = 'true'
});
$scope.getFileData = '';
// location.reload()
};
Html
The ng-file-select is used to get the file from the client.
This works fine for me. Hope this helps.
Note: I have used HTML tag instead of jade. Suitable changes applicable while using jade.
As far as I can guess, you are binding the FileReader.onload() method inside the saveUserImage function, then the onload method will be never called as the function is never binded instead a user calls saveUserImage method before editing the image. After that, no image will be selected as the onload() method will not execute.
Try coding Client Controller it this way
//This goes outside your method and will handle the file selection.This must be executed when your `input(type=file)` is created. Then we will use ng-init to bind it.
$scope.onAttachmentSelect = function(){
var logo = new FileReader(); // FileReader.
logo.onload = function (event) {
console.log("THE IMAGE HAS LOADED");
var file = event.currentTarget.files[0]
console.log("FILENAME:"+file.name);
$scope.image = file;
$scope.logoName = file.name; // Assigns the file name.
$scope.$apply();
//Call save from here
$scope.saveuserImage();
};
logo.readAsDataURL(file[0]);
$scope.file = file[0];
$scope.getFileData = file[0].name
reader.readAsDataURL(file);
};
//The save method is called from the onload function (when you add a new file)
$scope.saveuserImage = function(){
console.log("STARGING UPLOAD");
$scope.upload = $upload.upload({ // Using $upload
url: '/user/'+$stateParams.id+'/userImage',
method:'put'
data:, $scope.image
file: $scope.file
}).progress(function (evt) {})
.success(function () {
location.reload();
$scope.file = "";
$scope.hideUpload = 'true'
});
$scope.getFileData = '';
// location.reload()
};
The HTML.
//There is the ng-init call to binding function onAttachmentSelect
<div class="form-group">
<label>File Upload</label>
<input type="file" ng-init="onAttachmentSelect" ng-model="newResource.newUpload">
</div>
Hope this clue may help you
EDIT*
Will try to explain you the different Steps you must follow to check your code:
1.- Is your input[type=file] showing? If showing, please select an image
2.- Is your input calling the onload when the image selected has changed? (a console.log should be printed with my code version)
3.- If it has been called. Make the operations you need before sending, inside the onload method (if possible)
4.- When this method has finished doing desired changes. Inform with ng-model or however you want, a variable in the object you prepared to upload, with the base64 string generated in the onload method.
When arriving this point, remember checking that:
As very big images could be sent over json with base64, it´s very important to remember changing the minimum json size in Express.js for your app to prevent rejects. This is done, for example in your server/app.js as this:
var bodyParser = require('body-parser');
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb'}));
Remember also that the method reader.readAsDataURL(file) will give you a base64 string that could act as src of the image. You don´t need more than this. This base64 is what you can save in mongoose. Then, you can use ng-model to send a variable containing the base64 in the form with the "submit" button.
Then, in the Express.js endpoint that will handle your form, you will be able to decode the base64 string to a file, or to save the base64 directly on mongoose (storing images in the db is not much recommended if a lot of images is being to be loaded, or big ones desired, as the mongoDB query will be very slow).
Hope you can solve with those indications. If you still have some doubts, please comment and I´ll try to help
I'm also a noob using MEANJS, and this is how I made it work using ng-flow + FileReader:
HTML input:
<div flow-init
flow-files-added="processFiles($files)"
flow-files-submitted="$flow.upload()"
test-chunks="false">
<!-- flow-file-error="someHandlerMethod( $file, $message, $flow )" ! need to implement-->
<div class="drop" flow-drop ng-class="dropClass">
<span class="btn btn-default" flow-btn>Upload File</span>
<span class="btn btn-default" flow-btn flow-directory ng-show="$flow.supportDirectory">Upload Folder</span>
<b>OR</b>
Drag And Drop your file here
</div>
controller:
$scope.uploadedImage = 0;
// PREPARE FILE FOR UPLOAD
$scope.processFiles = function(flow){
var reader = new FileReader();
reader.onload = function(event) {
$scope.uploadedImage = event.target.result;
};
reader.onerror = function(event) {
console.error('File could not be read! Code ' + event.target.error.code);
};
reader.readAsDataURL(flow[0].file);
};
And on the server side the variable on the model receiving the value of uploadedImage is just of type string.
Fetching it back from the server didn't require any conversion:
<img src={{result.picture}} class="pic-image" alt="Pic"/>
Now just need to find out what to do with big files...
Related
I am trying to upload files through my web app using the following code.
View:
<form name="uploadForm" class="form-horizontal col-sm-12">
<div class="col-sm-4">
<input type="file" ng-model="rsdCtrl.viewData.file" name="file"/>
</div>
<div class="col-sm-4">
<button class="btn btn-success" type="submit" ng-click="uploadFile()">Upload</button>
</div>
</form>
Controller:
function uploadFile(){
if (uploadForm.file.$valid && file) {
return uploadService.upload(vd.file, "Convictions Calculator", "PCCS").then(function(response){
/* Some stuff */
}).catch(handleServiceError);
}
}
uploadService:
(function (){
'use strict';
angular.module('cica.common').service('uploadService', ['$http', '$routeParams', uploadService]);
function uploadService($http, $routeParams) {
this.upload = function (file, name, type) {
const fd = new FormData();
fd.append('document', file);
fd.append('jobId', $routeParams.jobId);
fd.append('documentRename', name);
fd.append('documentType', type);
return $http.post('/document/upload', fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).catch(function(err){
handleHttpError('Unable to upload document.', err);
});
};
}
})();
routes.js:
'POST /document/upload': {controller: 'DocumentController', action: 'uploadDocument'},
DocumentController:
"use strict";
const fs = require('fs');
module.exports = {
uploadDocument: function (req, res) {
console.log(req.allParams()); //Inserted as part of debugging
const params = req.allParams();
req.file('document').upload({
// don't allow the total upload size to exceed ~100MB
maxBytes: 100000000
}, function whenDone(err, uploadedFiles) {
if (err) {
return res.serverError(err);
}
// If no files were uploaded, respond with an error.
else if (uploadedFiles.length === 0) {
return res.serverError('No file was uploaded');
} else {
const filePath = uploadedFiles[0].fd;
const filename = uploadedFiles[0].filename;
return fs.readFile(filePath, function (err, data) {
if (err) {
return res.serverError(err);
} else {
const jobId = params.jobId;
const jobVars =
{
filePath: results.filePath,
fileName: params.documentRename,
fileType: params.documentType
};
return DocumentService.uploadConvictions(req.session.sessionId, jobId, jobVars).then(function (response) {
return res.send("Document uploaded.");
}).catch(function (err) {
return res.serverError(err);
});
}
});
}
});
},
If I upload a .jpeg (around 11kB) the upload works exactly as expected, however, if I try to upload a larger .jpeg (around 170kB) it falls over. There is no immediate error thrown/caught though, what happens is the formData object created in the upload service seems to lose its data. If I breakpoint on its value, it returns empty for the larger file, which eventually causes an error when the function tries to use these variables further on. Is there some kind of limit set to the size of a file you can upload via this method, or have I configured this incorrectly?
I take the chance and assume you are using bodyParser as middleware. bodyParser has a default limit of 100kb. Look at node_modules/body-parser/lib/types/urlencoded.js :
var limit = typeof options.limit !== 'number'
? bytes(options.limit || '100kb')
: options.limit
You can change the limit in your app.js by
var bodyParser = require('body-parser');
...
app.use(bodyParser.urlencoded( { limit: 1048576 } )); //1mb
I use this workaround...
HTML:
<input type="file" style="display:none" value="" id="uploadNewAttachment"/>
JavaScript:
In JavaScript you can upload files using the 3 method:
var binBlob = []; // If you use AngularJS, better leave it out of the DOM
var fi = document.getElementById('uploadNewAttachment');
fi.onchange = function(e) {
r = new FileReader();
r.onloadend = function(ev) {
binBlob[binBlob.length] = ev.target.result;
};
//r.readAsDataURL(e.target.files[0]); // Very slow due to Base64 encoding
//r.readAsBinaryString(e.target.files[0]); // Slow and may result in incompatible chars with AJAX and PHP side
r.readAsArrayBuffer(e.target.files[0]); // Fast and Furious!
};
$(fi).trigger('click');
What we have, javascript side is an Uint8Array of byte with values from 0 to 255 (or a Int8Array -128 to 127).
When this Array is sent via AJAX, it is "maximized" using signs and commas. This increases the number of total bytes sent.
EX:
[123, 38, 98, 240, 136, ...] or worse: [-123, 38, -81, 127, -127, ...]
As you can see, the number of characters transmitted is oversized.
We can instead proceed as follows:
Before send data over AJAX, do this:
var hexBlob = [];
for(var idx=0; idx<binBlob.length; idx++) {
var ex = Array.from(new Uint8Array(binBlob[idx]));;
for(var i=0;i<ex.length; i++) {
ex[i] = ex[i].toString(16).padStart(2,'0');
};
hexBlob[idx] = ex.join('');
}
What you have now, is a string of hex bytes in chars!
Ex:
3a05f4c9...
that use less chars of a signed or unsigned javascript array.
PHP:
On the PHP side, you can decode this array, directly to binary data, simply using:
for($idx=0; $idx<=count($hexBlob); $idx++) {
// ...
$binData = pack('H*',$hexBlob[$idx]);
$bytesWritten = file_put_contents($path.'/'.$fileName[$idx], $binData);
//...
}
This solution worked very well for me.
Avoid using the FormData API when Uploading Large Files1
The FormData API encodes data in base64 which add 33% extra overhead.
Instead of sending FormData, send the file directly:
app.service('fileUpload', function ($http) {
this.uploadFileToUrl = function (url, file) {
̶v̶a̶r̶ ̶f̶d̶ ̶=̶ ̶n̶e̶w̶ ̶F̶o̶r̶m̶D̶a̶t̶a̶(̶)̶;̶
̶f̶d̶.̶a̶p̶p̶e̶n̶d̶(̶'̶f̶i̶l̶e̶'̶,̶ ̶f̶i̶l̶e̶)̶;̶
̶r̶e̶t̶u̶r̶n̶ ̶$̶h̶t̶t̶p̶.̶p̶o̶s̶t̶(̶u̶r̶l̶,̶ ̶f̶d̶,̶ ̶{̶
return $http.post(url, file, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
});
};
});
When the browser sends FormData, it uses 'Content-Type': multipart/formdata and encodes each part using base64.
When the browser sends a file (or blob), it sets the content type to the MIME-type of the file (or blob). It puts the binary data in the body of the request.
How to enable <input type="file"> to work with ng-model2
Out of the box, the ng-model directive does not work with input type="file". It needs a directive:
app.directive("selectNgFile", function() {
return {
require: "ngModel",
link: function postLink(scope,elem,attrs,ngModel) {
elem.on("change", function(e) {
var files = elem[0].files[0];
ngModel.$setViewValue(files);
})
}
}
});
Usage:
<input type="file" select-ng-file ng-model="rsdCtrl.viewData.file" name="file"/>
Edit: NEVERMIND - I made a small mistake in the Angular service. My bad.
I'm trying to teach myself more backend by building a simple CMS using angular, Node and Express, and PostgreSql. I figured out how to do achieve all the CRUD functionality except UPDATE. I thought I understood what to do but I can't figure out where I'm going wrong. I keep getting a 404. What am I misunderstanding or going about the wrong way? I know this is simple but I'm pretty new so any help in understanding where I'm getting confused is really appreciated. Here's the relevant code:
HTML
<form ng-submit="updateBlogEntry(specificBlog.id, specificBlog.title, specificBlog.author, specificBlog.imageurl, specificBlog.content)">
<h2>Title:</h2>
<input type="text" ng-model="specificBlog.title"></input>
<br>
<h3>Author:</h3>
<input type="text" ng-model="specificBlog.author"></input>
<br>
<h3>Photo:</h3>
<input type="text" ng-model="specificBlog.imageurl"></input>
<br>
<h3>Content:</h3>
<textarea type="text" rows="5" cols="50" ng-model="specificBlog.content">
</textarea>
<br>
<button type="submit">Save Changes</button>
</form>
Angular Controller
var id = $stateParams.id;
var title = $stateParams.title;
var author = $stateParams.author;
var imageurl = $stateParams.imageurl;
var content = $stateParams.content;
$scope.updateBlogEntry = function(id, title, author, imageurl, content) {
adminService.updateBlogEntry(id, title, author, imageurl, content);
}
Angular Service
this.updateBlogEntry = function(id, title, author, imageurl, content) {
return $http({
method: 'PUT',
url: 'updateBlogEntry/' + id,
data: {
id: id,
title: title,
author: author,
imageurl: imageurl,
content: content
}
})
.success(function(data) {
alert("Entry Updated");
})
.error(function(data) {
alert("Error Updating");
})
Server Index
// EXTERNAL MODULES //
var express = require('express');
var bodyParser = require('body-parser');
var session = require('express-session');
var massive = require('massive');
// CONFIG //
var config = require('./config');
// EXPRESS //
var app = module.exports = express();
app.use(express.static(__dirname + './../dist'));
app.use(bodyParser.json());
// MASSIVE //
var massiveUri = config.MASSIVE_URI;
var massiveServer = massive.connectSync({
connectionString: massiveUri
});
app.set('db', massiveServer);
var db = app.get('db');
var dbSetup = require('./services/dbSetup');
dbSetup.run();
// CONTROLLERS //
var userCtrl = require('./controllers/userCtrl');
var blogCtrl = require('./controllers/blogCtrl');
// Blog Endpoints //
app.post('/api/createBlogEntry', blogCtrl.createBlogEntry);
app.get('/api/getBlogEntries', blogCtrl.readBlogEntries);
app.get('/api/getBlogEntry/:id', blogCtrl.readBlogEntry);
// BUG Why isn't this working?
app.put('/api/updateBlogEntry/:id', blogCtrl.updateBlogEntry);
// CONNECTIONS //
var port = config.PORT;
app.listen(port, function() {
console.log('Listening on port ' + port);
});
Node Controller
updateBlogEntry: function(req, res, next){
db.blogs.blog_update([
req.params.id,
req.body.title,
req.body.author,
req.body.imageurl,
req.body.content
],
function(err, results){
if (err){
console.error(err);
res.send(err);
} else {
res.send(results[0]);
}
})
}
blog_update.sql
UPDATE blogs
set
title = COALESCE($2, title),
author = COALESCE($3, author),
imageurl = COALESCE($4, imageurl),
content = COALESCE($5, content)
WHERE id = $1
RETURNING * ;
The error in the console:
angular.js:11881 PUT http://localhost:3000/updateBlogEntry/1 404 (Not Found)
You have written your URL wrong.
It should be /api/updateBlogEntry as per your Node's express routes.
change this part
` url: 'updateBlogEntry/' + id,`
It should be
url: '/api/updateBlogEntry/' + id,
How would i gather that info im sending from the client? in this case, the id?
How can I get the id?
I do use client sided request:
return $http.post('/api/kill', {id:4}, {
headers: {}
})
and when i check server sided for req.body console.log(Req.body) i do get:
{ '{"id":4}': '' }
req.body.id returns:
undefined
How can i get the id of 4?
EDIT1:
the main code is located at https://github.com/meanjs/mean
server sided code:
app.post('/api/kill', function (req, res) {
console.log(req.body); // { '{"id":4}': '' }
console.log(req.body.id); // undefined
});
You need to assign that id property to an object like
item = { id : 4 }
Lets suppose you have a text-box and the user wants to save a new item by inserting its name in it and click on submit.
Lets also suppose you are using a MongoDB collection of items, which have only id field for simplicity.
Here's what you should do to get it going easy.
Make sure you are importing bodyParser
var bodyParser = require('body-parser');
HTML - saving a new item with custom id
<div class="form-group">
<label for="id">ID</label>
<input type="text" class="form-control" id="id" ng-model="ItemController.formData.id">
</div>
<button type="submit" ng-click="ItemController.createItem()" >Submit</button>
Angular part - ItemController.js
'use strict';
angular
.module('myApp')
.controller('ItemController', ItemController);
function ItemController($http) {
var vm = this;
/** Creates a New Marker on submit **/
vm.createItem = function() {
// Grabs all of the text box fields
var itemData = {
id : vm.formData.id
};
// Saves item data to the db
$http.post('/api/kill', itemData)
.success(function(response) {
if(response.err){
console.log('Error: ' + response.err);
} else {
console.log('Saved '+response);
}
});
};
}
Route Handling - routes.js
var ItemFactory = require('./factories/item.factory.js');
// Opens App Routes
module.exports = function(app) {
/** Posting a new Item **/
app.post('/api/kill', function(req, res) {
ItemFactory.postItem(req).then( function (item) {
return res.json(item);
});
});
};
Post into MongoDB - item.factory.js
var Item = require('../models/item-model');
exports.postItem = postItem;
function postItem(item) {
return new Promise( function (resolve, reject) {
var newItem = new Item(item.body);
newItem.save(function(err) {
if (err){
return reject({err : 'Error while saving item'});
}
// If no errors are found, it responds with a JSON of the new item
return resolve(item.body);
});
});
}
If you try console.log() on the different pieces of code where I passed the item, you can properly see an object with id property.
I hope I've been helpful.
you miss the single quote :
var obj = { 'id':4 };
console.log(obj.id); //display 4
in your example :
return $http.post('/api/kill', {'id':4}, {
headers: {}
})
response you are getting is not in object form
{ '{"id":4}': '' }
it is a key value pair and key is a string
'{"id":4}'
In order to get the right value at your end your json response shoulb be like
{ { 'id':4 } }
and then it will work like
console.log(req.body); // { {"id":4} }
console.log(req.body.id); // 4
Make sure that you enabled JSON body parser in your node.js application.
var bodyParser = require('body-parser');
....
app.use(bodyParser.json());
I've recently started using MEAN Stack to create a basic application, so im a total beginner, I set up my controllers and everything and I want to post, however it returns api/user not found, can someone pinpoint the problem and help me out? Thanks.
Server.js:
var app = require ('./app/app');
var signupController = require ('./server/signup-controller');
app.post('api/users', signupController.create);
app.listen('8080', function(){
console.log('[OK] => HTTP Server listening on http://localhost:8080');
require('./app/db').init('mongodb://localhost:27017/shopialmedia' );
});
Server side Controller (signup-controller.js):
module.exports.create = function (req, res) {
console.log(req.body);
}
Client Side Controller (signup-controller.js):
app.controller('signupController', ['$scope', '$resource', function ($scope, $resource) {
var User = $resource('/api/users');
$scope.createUser = function () {
var user = new User();
user.email = $scope.userEmail;
user.password = $scope.userPass;
user.firstName = $scope.userFName;
user.lastName = $scope.userLName;
user.age = $scope.userAge;
user.$save(function (result){
$scope.user.push(result);
$scope.userEmail = '';
$scope.userPass = '';
$scope.userFName = '';
$scope.userLName = '';
$scope.userAge = '';
});
}
}]);
My module :
var app = angular.module('signupApp', ['ngResource']);
My app.js :
var express = require ('express');
var app = express();
app.use(express.static('public'));
require('./routes')(app);
module.exports = app;
When I go to run the application on my web page and submit the information, it returns api/user 404 Not found any suggestions on what to do, I'd greatly appreciate it. As I said im a beginner so please take that into consideration.
Server.js Add:
app.get('api/users', signupController.list);
signup-controller.js Add:
mongoose = require('mongoose'),
User = mongoose.model('User');
module.exports.list = function(req, res){
var searcher = req.query || {};
User.find(searcher)
.sort('-created')
.exec(function (err, users) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
}
res.json(users);
});
};
Obviously, you'll also have to connect to mongoose with a file probably called main.js then you'll need a UserModel.js (or equivalent) where you can define the attributes of your user etc using the mongoose Schema...
I mean you're missing a lot if you want something a little simpler to start you can just do:
module.exports.list = function(req, res){
var users = [];
res.json(users);
};
Also think you need this and this is also nicer formatting:
var router = express.Router();
router.route('/')
.get(users.list)
.get(users.create)
app.use('/api/users', router);
Not sure what this is supposed to do or what ./routes is: require('./routes')(app);
I´m making an angular application, which gives users the possibilities, to manage their projects. I´ve got nodeJS & express serveside and MongoDB as my database.
I want to achieve, that a user can upload media(images under 16MB, so no need to use GridFS) to their projects and that you can display which project, has which media attached.
I´m not getting the images, nor an error. How can I pass the project_Id from angular, to my route, to find the media attached to the project? Is this way of trying to POST and GET the media the right way?
The model for projects and for media:
var mediaSchema = mongoose.Schema({
media : {data: Buffer, contentType: String},
project_id : String,
updated_at : {type: Date, default: Date.now }
});
var projectSchema = mongoose.Schema({
author : String,
name : String,
description : String,
tags : String,
updated_at : {type: Date, default: Date.now },
active : {type: Boolean, default: false}
});
The routing
var Media = require('./models/media.js');
//GET all the media
app.get('/uploads/', function(req, res, next){
Media.find(function (err, media){
if (err) return next (err);
res.json(media);
});
});
//GET one item
app.get('/uploads/media/:projectId', function(req, res, next){
Media.findOne(req.params , function (err, media){
if (err) return next (err);
res.json(media);
});
});
Managing the uploads
app.use(multer({ dest: './uploads/',
rename: function (fieldname, filename) {
return filename+Date.now();
},
onFileUploadStart: function (file) {
console.log(file.originalname + ' is starting ...')
},
onFileUploadComplete: function (file) {
console.log(file.fieldname + ' uploaded to ' + file.path)
done=true;
}
}));
var Media = require('./app/models/media.js');
//POST media to the upload directory
app.post('/uploads/', function(req, res){
if(done==true){
Media.create(req.body, function(err, post){
console.log(req.files);
res.end('File uploaded');
});
}
});
Angular Controller
var app = angular.module('myApp', []);
app.controller('projectCtrl', function($scope, $http) {
$scope.myVar = false;
$scope.toggle = function() {
$scope.myVar = !$scope.myVar
};
$http.get('/profile/project/').then(function (res){
$scope.projects = res.data;
});
//GET Media
var projectId = {{projects._id}};
$http.get('/uploads/media' + projectId).succes(function(data){
console.log('Medien-Daten erhalten');
$scope.media = data;
});
});
Kind regards from Germany,
David
Update (new Errors)
Trying to implement a solution, I get problems with my scopes. When I´m adding the $http.get for the media, my other scope seem to fetch no data... they are shown like this:
Update 2(scope error fixed)
Fixed the error in the controller. I hadn´t defined a var for projectId, that caused the error.
Managed to make the GET request work, and my application is looking for entries in the database. But i can´t manage to see any..
Your use of .find is incorrect in the get all function.
See http://mongoosejs.com/docs/api.html#query_Query-find
Media.find({}, function (err, media){
if (err) return next (err);
res.json(media);
});
This will return all documents.