I'm making an API server with Express, Graphql (Apollo server) and mongoose.
I'm testing the user creation. When the email is duplicated mongoose throws an error (Validation error. Unique = true) and graphql handles very good. But the console (terminal) shows the error too. How can i prevent that issue?
Resolver:
const MODEL_PATH = '../../models';
const User = require(MODEL_PATH + '/User');
const { register } = require('../../controllers/auth/RegisterController');
module.exports = {
RootQuery: {
users() {
return User.find({});
}
},
Mutation: {
registerUser(_, data) {
return register(data);
}
}
};
RegisterController (register function)
exports.register = function(data) {
const { email, password } = data;
const user = new User({
email,
password
});
return new Promise((resolve, reject) => {
user.save().then((user) => {
resolve(user);
}).catch((err) => {
reject(err);
});
});
};
And the error in the console (I DON'T WANT THAT. I HANDLED THE ERROR IN THE CONTROLLER. I WANT THE ERROR ONLY IN THE GRAPHQL RESPONSE)
MongoError: E11000 duplicate key error collection: y.users index: email_1 dup key: { : "test#example.com" }
at Function.MongoError.create (/Volumes/Datos/x/code/y/server/node_modules/mongodb-core/lib/error.js:31:11)
at toError (/Volumes/Datos/x/code/y/server/node_modules/mongodb/lib/utils.js:114:22)
....
Response in Graphiql (That is ok)
{
"data": {
"registerUser": null
},
"errors": [
{
"message": "E11000 duplicate key error collection: y.users index: email_1 dup key: { : \"test#example.com\" }",
"locations": [
{
"line": 9,
"column": 3
}
],
"path": [
"registerUser"
]
}
]
}
Thank you
According to: http://dev.apollodata.com/tools/graphql-server/setup.html
in server
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema: myschema, debug: false }));
debug false did the trick.
Now. I'm check the packages for better error handling
Thanks
check out apollo-errors:
https://github.com/thebigredgeek/apollo-errors
and apollo-resolvers:
https://github.com/thebigredgeek/apollo-resolvers
These packages, together, were made to handle issues like the one you describe
Related
I am trying to integrate Pusher with the Wix HTTP Functions
For example:
A GET request is made to the Wix site ( path: '/findItems' ). After the request is made, I want to check for new insertion of items in the database. This, I found, I could do with the afterInsert hook. When the hook is hooked, I want to trigger the Pusher.
This is the code I am currently using http-functions.js:
import { ok, created, notFound, serverError } from 'wix-http-functions';
import wixData from 'wix-data';
import Pusher from "pusher";
const pusher = new Pusher({
appId: "xxxxx",
key: "xxxxxxxxxxxxxxx",
secret: "xxxxxxxxxxxx",
cluster: "xxxx",
useTLS: true
});
export function get_findItems(request) {
let options = {
"headers": {
"Content-Type": "application/json"
}
};
return wixData.query("users")
.eq("firstName", request.path[0])
.eq("lastName", request.path[1])
.find()
.then((results) => {
if (results.items.length > 0) {
options.body = {
"items": results.items
};
return ok(options);
}
options.body = {
"error": `'${request.path[0]} ${request.path[1]}' was not found`
};
return notFound(options);
})
.catch((error) => {
options.body = {
"error": error
};
return serverError(options);
});
}
export function post_newItem(request) {
let options = {
"headers": {
"Content-Type": "application/json"
}
};
return request.body.text()
.then( (body) => {
return wixData.insert("users", JSON.parse(body));
} )
.then( (results) => {
options.body = {
"inserted": results
};
return created(options);
} )
.catch( (error) => {
options.body = {
"error": error
};
return serverError(options);
} );
}
export function users_afterInsert(item, context) {
let hookContext = context;
pusher.trigger("channel", "action", {
firstName: item.firstName,
lastName: item.lastName
});
return item;
}
But unfortunately, Pusher does not get triggered. After Debugging, I found that the Pusher package is installed and working but not triggering only in the afterInsert hook!
Any help is greatly appreciated!
Thanks !
The code for the afterInsert hook needs to be in a backend file named data.js, not in the http-functions.js file as you have it now.
I'm having a problem identifying a 'task' in mongoDB from my frontend angular.
This question is the most similar to my question but here it just says req.body.id and doesn't really explain how they got that.
This question involves what I am trying to do: update one document in a collection upon a click. What it does in the frontend isn't important. I just want to change the status text of the Task from "Active" to "Completed" onclick.
First I create a task and stick it in my database collection with this code:
createTask(): void {
const status = "Active";
const taskTree: Task = {
_id: this._id,
author: this.username,
createdBy: this.department,
intendedFor: this.taskFormGroup.value.taskDepartment,
taskName: this.taskFormGroup.value.taskName,
taskDescription: this.taskFormGroup.value.taskDescription,
expectedDuration: this.taskFormGroup.value.expectedDuration,
status: status
};
this.http.post("/api/tasks", taskTree).subscribe(res => {
this.taskData = res;
});
}
When I make this post to the backend, _id is magically filled in!
I'm just not sure how I can pass the id to the put request in nodejs router.put('/:id') when I'm pushing it from the frontend like this:
completeTask(): void {
const status = "Completed";
const taskTree: Task = {
_id: this._id,
author: this.username,
createdBy: this.department,
intendedFor: this.taskFormGroup.value.taskDepartment,
taskName: this.taskFormGroup.value.taskName,
taskDescription: this.taskFormGroup.value.taskDescription,
expectedDuration: this.taskFormGroup.value.expectedDuration,
status: status
};
console.log(taskTree);
this.http.put("/api/tasks/" + taskTree._id, taskTree).subscribe(res => {
this.taskData = res;
console.log(res);
});
}
In the template I have a form that's filled in and the data is immediately outputted to a task 'card' on the same page.
When I send the put request from angular, I get a response in the backend just fine of the response I ask for in task-routes.js:
router.put("/:id", (req, res, next) => {
const taskData = req.body;
console.log(taskData);
const task = new Task({
taskId: taskData._id,
author: taskData.author,
createdBy: taskData.createdBy,
intendedFor: taskData.intendedFor,
taskName: taskData.taskName,
taskDescription: taskData.taskDescription,
expectedDuration: taskData.expectedDuration,
status: taskData.status
})
Task.updateOne(req.params.id, {
$set: task.status
},
{
new: true
},
function(err, updatedTask) {
if (err) throw err;
console.log(updatedTask);
}
)
});
The general response I get for the updated info is:
{
author: 'there's a name here',
createdBy: 'management',
intendedFor: null,
taskName: null,
taskDescription: null,
expectedDuration: null,
status: 'Completed'
}
Now I know _id is created automatically in the database so here when I click create task & it outputs to the 'card', in the console log of task after I save() it on the post request, taskId: undefined comes up. This is all fine and dandy but I have to send a unique identifier from the frontend Task interface so when I send the 'put' request, nodejs gets the same id as was 'post'ed.
I'm quite confused at this point.
So I finally figured this out...In case it helps someone here's what finally worked:
First I moved my update function and (patch instead of put) request to my trigger service:
Trigger Service
tasks: Task[] = [];
updateTask(taskId, data): Observable<Task> {
return this.http.patch<Task>(this.host + "tasks/" + taskId, data);
}
I also created a get request in the trigger service file to find all the documents in a collection:
getTasks() {
return this.http.get<Task[]>(this.host + "tasks");
}
Angular component
Get tasks in ngOnInit to list them when the component loads:
ngOnInit() {
this.triggerService.getTasks().subscribe(
tasks => {
this.tasks = tasks as Task[];
console.log(this.tasks);
},
error => console.error(error)
);
}
Update:
completeTask(taskId, data): any {
this.triggerService.updateTask(taskId, data).subscribe(res => {
console.log(res);
});
}
Angular template (html)
<button mat-button
class="btn btn-lemon"
(click)="completeTask(task._id)"
>Complete Task</button>
// task._id comes from `*ngFor="task of tasks"`, "tasks" being the name of the array
//(or interface array) in your component file. "task" is any name you give it,
//but I think the singular form of your array is the normal practice.
Backend Routes
GET all tasks:
router.get("", (req, res, next) => {
Task.find({})
.then(tasks => {
if (tasks) {
res.status(200).json(tasks);
} else {
res.status(400).json({ message: "all tasks not found" });
}
})
.catch(error => {
response.status(500).json({
message: "Fetching tasks failed",
error: error
});
});
});
Update 1 field in specified document (status from "Active" to "Completed"):
router.patch("/:id", (req, res, next) => {
const status = "Completed";
console.log(req.params.id + " IT'S THE ID ");
Task.updateOne(
{ _id: req.params.id },
{ $set: { status: status } },
{ upsert: true }
)
.then(result => {
if (result.n > 0) {
res.status(200).json({
message: "Update successful!"
});
}
})
.catch(error => {
res.status(500).json({
message: "Failed updating the status.",
error: error
});
});
});
Hope it helps someone!
I am trying to store JSON array to the existing document with the id who logged in. I don't have an idea how to post this array to backend.
cake.component.ts
export class CakeComponent implements OnInit {
form: FormGroup;
constructor(
public fb: FormBuilder,
private api: ApiService
) { }
ngOnInit() {
this.submitForm();
}
submitForm() {
this.form = this.fb.group({
url: ['', [Validators.required]],
width : ['', [Validators.required]],
height: ['', [Validators.required]]
})
}
submitForm() {
if (this.form.valid) {
this.api.AddCake(this.form.value).subscribe();
}
}
}
Existing MongoDB document Cakes
{
"id": "0001",
"type": "donut",
"name": "Cake"
}
Expected Output
{
"id": "0001",
"type": "donut",
"name": "Cake",
"image": {
"url": "images/0001.jpg",
"width": 200,
"height": 200
}
}
Here is the basic code, please update it accordingly :
/** You can split this code into multiple files, schema into a file &
mongoDB connectivity into a common file & actual DB update can be placed where ever you want */
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
const cakeSchema = new Schema({
id: String,
type: String,
name: String,
image: {
url: String,
width: Number,
height: Number
}
});
const cakeModel = mongoose.model('Cakes', cakeSchema, 'Cakes');
let form = {
"url": "images/0001.jpg",
"width": 200,
"height": 200
}
async function myDbConnection() {
const url = 'yourDBConnectionString';
try {
await mongoose.connect(url, { useNewUrlParser: true });
console.log('Connected Successfully')
let db = mongoose.connection;
// You can use .update() or .updateOne() or .findOneAndUpdate()
let resp = await cakeModel.findOneAndUpdate({ id: '0001' }, { image: form }, { new: true });
console.log('successfully updated ::', resp)
db.close();
} catch (error) {
console.log('Error in DB Op ::', error);
db.close();
}
}
module.exports = myDbConnection();
Update :
In case if you're using mongoDB driver but not mongoose :
const MongoClient = require('mongodb').MongoClient;
// Connection URL
const url = 'yourDBConnectionString';
// Database Name
const dbName = 'test';
// Create a new MongoClient
const client = new MongoClient(url);
let form = {
"url": "images/0001.jpg",
"width": 200,
"height": 200
}
// Use connect method to connect to the Server
client.connect(async function (err) {
if (err) console.log('DB connection error ::', err)
console.log("Connected successfully to server");
try {
// You can use .update() or .updateOne() or .findOneAndUpdate()
let resp = await client.db(dbName).collection('Cakes').findOneAndUpdate({ id: '0001' }, { $set: { image: form } }, { returnOriginal: false });
console.log('successfully updated ::', resp , 'resp value ::', resp.value)
client.close();
} catch (error) {
console.log('Error in DB Op ::', error);
client.close();
}
});
I am connecting GraphQL with REST endpoints, I confirmed that whenever I am calling http://localhost:3001/graphql it is hitting REST endpoint and it is returning JSON response to GraphQL server, but I am getting an empty response from GraphQL server to GUI as follows:
{
"data": {
"merchant": {
"id": null
}
}
}
Query (decoded manually):
http://localhost:3001/graphql?query={
merchant(id: 1) {
id
}
}
Below is how my GraphQLObjectType looks like:
const MerchantType = new GraphQLObjectType({
name: 'Merchant',
description: 'Merchant details',
fields : () => ({
id : {
type: GraphQLString // ,
// resolve: merchant => merchant.id
},
email: {type: GraphQLString}, // same name as field in REST response, so resolver is not requested
mobile: {type: GraphQLString}
})
});
const QueryType = new GraphQLObjectType({
name: 'Query',
description: 'The root of all... queries',
fields: () => ({
merchant: {
type: merchant.MerchantType,
args: {
id: {type: new GraphQLNonNull(GraphQLID)},
},
resolve: (root, args) => rest.fetchResponseByURL(`merchant/${args.id}/`)
},
}),
});
Response from REST endpoint (I also tried with single object in JSON instead of JSON array):
[
{
"merchant": {
"id": "1",
"email": "a#b.com",
"mobile": "1234567890"
}
}
]
REST call using node-fetch
function fetchResponseByURL(relativeURL) {
return fetch(`${config.BASE_URL}${relativeURL}`, {
method: 'GET',
headers: {
Accept: 'application/json',
}
})
.then(response => {
if (response.ok) {
return response.json();
}
})
.catch(error => { console.log('request failed', error); });
}
const rest = {
fetchResponseByURL
}
export default rest
GitHub: https://github.com/vishrantgupta/graphql
JSON endpoint (dummy): https://api.myjson.com/bins/8lwqk
Edit: Adding node.js tag, may be issue with promise object.
Your fetchResponseByURL function get empty string.
I think the main problem is that you are using wrong function to get the your JSON string, please try to install request-promise and use it to get your JSON string.
https://github.com/request/request-promise#readme
something like
var rp = require('request-promise');
function fetchResponseByURL(relativeURL) {
return rp('https://api.myjson.com/bins/8lwqk')
.then((html) => {
const data = JSON.parse(html)
return data.merchant
})
.catch((err) => console.error(err));
// .catch(error => { console.log('request failed', error); });
}
In this case using data.merchant solved my problem. But the above suggested solution i.e., use of JSON.parse(...) might not be the best practice because if there are no object in JSON, then expected response might be as follows:
{
"data": {
"merchant": null
}
}
Instead of fields to be null.
{
"data": {
"merchant": {
"id": null // even though merchant is null in JSON,
// I am getting a merchant object in response from GraphQL
}
}
}
I have updated my GitHub: https://github.com/vishrantgupta/graphql with working code.
I'm building a Node Express app, with Postgres as DB and Sequelize as ORM.
I have a router.js file:
router.route('/publish')
.put((...args) => controller.publish(...args));
controller.js which looks like this:
publish(req, res, next) {
helper.publish(req)
.then((published) => {
res.send({ success: true, published });
});
}
And a helper.js
publish(req) {
return new Promise((resolve, reject) => {
Article.findAll({
where: { id: req.query.article_id },
attributes: ['id', 'state']
})
.then((updateState) => {
updateState.updateAttributes({
state: 2
});
})
.then((updateState) => {
resolve(updateState);
});
});
}
So for example when I hit PUT http://localhost:8080/api/publish?article_id=3555 I should get:
{
"success": true,
"published": [
{
"id": 3555,
"state": 2
}
]
}
The current state of the article is 1.
However, I get the following error Unhandled rejection TypeError: updateState.updateAttributes is not a function. When I remove the updateState.updateAttributes part from my helper.js I get the response with the current state.
How do I update the state of the article correctly?
You should just change findAll with findOne , as you are just trying to find specific article by id :
Article.fineOne({ //<--------- Change here
where: { id: req.query.article_id },
attributes: ['id', 'state']
})
.then((updateState) => {
updateState.updateAttributes({state: 2}); //<------- And this will work
})
But if you still want to go with findAll and to know how to use that , Please try this and read the comments , that will clear all your doubts :
Article.findAll({
where: { id: req.query.article_id },
attributes: ['id', 'state']
})
.then((updateState) => {
// updateState will be the array of articles objects
updateState.forEach((article) => {
article.updateAttributes({ state: 2 });
});
//-------------- OR -----------------
updateState.forEach((article) => {
article.update({ state: 2 });
});
})