How to manage data template when serving an api using Node.js - api

I'm currently trying to create an api for my web app using Node.js. The api intended to return JSON data to the api user.
For example, i'm having following endpoint / object:
- Articles (collection of article)
- Article (single article, each article would be having tags)
- Tags
Each endpoint / object having they own json data format need to return, in example:
Articles: [{articleObject}, {articleObject}, {articleObject}]
Article
{
id: 12,
title: 'My awesome article *i said and i know it',
content: 'blah blah blah blah',
tags: [{tagObject}, {tagObject}, {tagObject}]
}
Each endpoint can call to other endpoint to get needed data, in example:
Article endpoint can calling the Tags endpoint to get the article tags collection, the Tags endpoint will be returning Array Object.
My questions:
How to manage the Object structures, so if a endpoint called from another endpoint it will return Array / Object, while it will be return JSON string when called from api user.
Should I create Object template for each endpoint. The process will be:
Endpoint will be calling Object Template;
The Object Template will fetching data from database;
Endpoint will return the Object Template when called from other endpoint;
Endpoint will call template engine to parse the Object Template to JSON string for api user. JSON string view will be provided for each endpoint.
What Template Engine can process below Object Template and treat them as variables, in PHP i'm using Twig that can receive PHP Object and treat them as variables. Here you can find how i do that using Twig
Object Template file content example:
var Tags = require('./tags');
module.exports = function(param){
/* do fetching data from database for specified parameters */
/* here */
var data = {};
return {
id: function() {
return data.id;
},
title: function() {
return data.title;
},
description: function() {
return data.description;
},
tags: function() {
return Tags(data.id);
}
}
}
Hope someone can show me the way for it.

I use express to do my apis...you can do something like this:
app.get('/api/article', authenticate, routes.api.article.get);
authenticate is just a middleware function that authenticates the user, typically this is grabbing the user from session and making sure they are logged in.
inside ./routes/api.js
var Article = require('../models/article.js');
exports.api = {}
exports.api.article = {}
exports.api.article.get = function(req, res){
//get article data here
Article.find({created_by: req.session.user._id}).populate('tags').populate('created_by').exec(function(err, list){
res.json(list);
});
};
This assume you're using mongoose and mongodb and have an Article schema similar to this, which includes a reference to a Tag schema:
var ArticleSchema = new Schema({
name : { type: String, required: true, trim: true }
, description: { type: String, trim: true }
, tags : [{ type: Schema.ObjectId, ref: 'Tag', index: true }]
, created_by : { type: Schema.ObjectId, ref: 'User', index: true }
, created_at : { type: Date }
, updated_at : { type: Date }
});

Related

How To Use The Same GraphQL Query Multiple Times In A Single API Call. Amplify, DynamoDB

The user will input multiple usernames and I would like to search my DynamoDB table for all each user's id in a single API call.
The following is the important portion of the graphql user schema:
type User
#model
#key(name: "byName", fields: ["username"], queryField: "findUsername")
{
id: ID!
email: AWSEmail!
username: String!
firstName: String
lastName: String
createdAt: AWSDateTime
updatedAt: AWSDateTime
}
I would like to create a query which takes the list of usernames and returns each user's user id in a single api call. I am not sure how to dynamically and separately add the usernames to a query. Btw, I am using aws-amplify to help with dynamodb and graphql. This is also a React Native project.
You can run multiple queries and mutations using batch operations in AWS AppSync. Eg: BatchGetItem
type Post {
id: ID!
title: String
}
type Query {
batchGet(ids: [ID]): [Post]
}
Sample query
query get {
batchGet(ids:[1,2,3]){
id
title
}
}
batchGet.request.vtl
#set($ids = [])
#foreach($id in ${ctx.args.ids})
#set($map = {})
$util.qr($map.put("id", $util.dynamodb.toString($id)))
$util.qr($ids.add($map))
#end
{
"version" : "2018-05-29",
"operation" : "BatchGetItem",
"tables" : {
"Posts": {
"keys": $util.toJson($ids),
"consistentRead": true
}
}
}
batchGet.response.vtl
$util.toJson($ctx.result.data.Posts)
For more details check out this tutorial.
https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-batch.html

Syncfusion TreeGrid and Grid with WebAPI doesn't work on delete

I've set up a treeGrid (the grid is the same) to get data through the ASP.NET WebAPI using their DataManager:
var categoryID=15;
var dataManager = ej.DataManager({
url: "/API/myrecords?categoryID=" + categoryID,
adaptor: new ej.WebApiAdaptor()
});
$("#treeGridContainer").ejTreeGrid({
dataSource: dataManager,
childMapping: "Children",
treeColumnIndex: 1,
isResponsive: true,
contextMenuSettings: {
showContextMenu: true,
contextMenuItems: ["add", "edit", "delete"]
},
contextMenuOpen: contextMenuOpen,
editSettings: { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Normal', editMode: "rowEditing" },
columns: [
{ field: "RecordID", headerText: "ID", allowEditing: false, width: 20, isPrimaryKey: true },
{ field: "RecordName", headerText: "Name", editType: "stringedit" },
],
actionBegin: function (args) {
console.log('ActionBegin: ', args);
if (args.requestType === "add") {
//add new record, managed manually...
var parentID = 0;
if (args.level != 0) {
parentID = args.parentItem.TaxonomyID;
}
args.data.TaxonomyID = 0;
addNewRecord(domainID, parentID, args.data, args.model.selectedRowIndex);
}
}
});
The GET works perfectly.
The PUT works fine as I'm managing it manually because it's not called at all from the DataManager, and in any case I want to manage the update of the records in the TreeGrid.
The problem is with DELETE, that is called by the DataManager when I click Delete from the context menu over an item in the TreeGrid.
It makes a call to the following URL:
http://localhost:50604/API/myrecords?categoryID=15/undefined
and obviously, I get a 405 (Method Not Allowed)
The problem is given by the categoryID parameters that break the RESTful schema, and the DataManager is not able to understand that there is a parameter.
A possible solution could be to send this parameter as a POST variable but the DataManager is not able to do it.
Does anyone have a clue of how to solve it? it's a common scenario in real-world applications.
While populating Tree Grid data using ejDataManger, CRUD actions will be handled using inbuilt Post (insert), Put (update), Delete requestType irrespective of CRUD URL’s. So, no need to bind ‘removeUrl’ for deleting records.
And, in the provided code example parameter is passed in the URL to fetch data hence the reported issue occurs. Using ejQuery’s addParams method we can pass the parameter in URL. You can find the code example to pass the parameter using Tree Grid load event and the parameter is retrieved in server side using DataManager.
[html]
var dataManager = ej.DataManager({
url: "api/Values",
adaptor: new ej.WebApiAdaptor()
});
$("#treeGridContainer").ejTreeGrid({
load: function (args) {
// to pass parameter on load time
args.model.query.addParams("keyId", 48);
},
});
[controller]
public object Get()
{
var queryString = HttpContext.Current.Request.QueryString;
// here we can get the parameter during load time
int num = Convert.ToInt32(queryString["keyId"]);
//..
return new {Items = DataList, Count = DataList.Count() };
}
You can find the sample here for your reference.
Regards,
Syncfusion Team

How to add pre-hook in keystonejs?

I want to add multiple select options field. But the docs state that doesn't allow for multiple select. But recommends pre-hook for that case.
Stores a String or Number in the model. Displayed as a select field in
the Admin UI. Does not allow for multiple items to be selected. If you
want to provide multiple values, you can use TextArray or NumberArray,
although neither will have the same constrained input. You can limit
the options using a pre-save hook.
I search for pre-hook but it seems came from mongoose. And in my case, I create the model using Keystone so that I can use it in admin page
var keystone = require('keystone');
var Types = keystone.Field.Types;
var MyModel = new keystone.List('MyModel');
MyModel.add({
aField: { type: Types.TextArray, required: false, initial: true },
});
so how do I create the pre-hook? for example, I want to limit the TextArray to be set of ('a','b','c')?
I have set up pre-save hooks like this (or something similar to this. Did not test this code).
var keystone = require('keystone');
var Types = keystone.Field.Types;
/**
* Musician Model
* ==========
*/
var Musician = new keystone.List('Musician', {
map: { name: 'title' },
autokey: { path: 'slug', from: 'title', unique: true },
});
Musician.add({
title: { type: String, required: true },
published: { type: Types.Boolean, default: false },
musicianId: { type: String, note: noteUpdateId },
});
Musician.schema.pre('save', function (next) {
console.log(this.title);
console.log(this.isNew);
if (this.isNew) {
// generates a random ID when the item is created
this.musicianId = Math.random().toString(36).slice(-8);
}
next();
});
Musician.defaultColumns = 'title, published, musicianId';
Musician.register();

Is there buttons / funtion calls & tables handled in MetaWidget - js

I am wondering if some can help me out to get the buttons , function calls and tables displayed using metawidget jsonSchema.
Unfortunately, I can see it just as a form render-er for our applications, is it something which we need to define externally? also if we could navigate from one form to another somehow
<script type="text/javascript">
var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
inspector: new metawidget.inspector.CompositeInspector( [ new metawidget.inspector.PropertyTypeInspector(),
function( toInspect, type, names ) {
return {
properties:
name: {
required: true
},
notes: {
type: "string",
large: true
},
employer: {
type: "string",
section: "Work"
},
department: {
type: "string"
}
}
};
} ] ),
layout: new metawidget.jqueryui.layout.TabLayoutDecorator(
new metawidget.layout.TableLayout( { numberOfColumns: 2 } ))
} );
mw.toInspect = person;
mw.buildWidgets();
Above schema holds only properties to render the fields, but where to specify the functionalities?
Assuming the object you are inspecting has functions, in JSON schema you can specify the property with a type of function. Metawidget will render these as buttons (and will wire up their click handler).
Note that PropertyTypeInspector will find functions automatically, so you could also consider combining your JsonSchemaInspector with a PropertyTypeInspector using CompositeInspector.
Often your data object (e.g. var person = { firstname: 'Homer', surname: 'Simpson' } ) and your actions object (e.g. var personActions = { save: function() {...}, delete: function() {...}} ) are separate. In those cases you can position two Metawidgets as siblings (or nested within each other), each pointing at different objects.
Finally for tables use a type of array. In JSON schema, you nest an items object that further nests a properties object, to describe the properties of the array items. See this example http://blog.kennardconsulting.com/2016/04/metawidget-and-angular-arrays.html (it's for Angular, but the JSON Schema is the same)

Firebase make user object from auth data

So I'm using Angularfire in an ionic app and trying to figure out how to make a user object that is associated with the auth data from an Auth $createUser call. My first try had the auth call and the user got authenticated, then a user object was made and pushed into a $firebaseArray which works fine, but I don't know how to grab the current user after they are logged in to update, destory, or do anything with that users data. I have made it work with looping through the users array and matching the uid from the user array item and the auth.uid item which was set to be the same in the user array object creation. This seems really ineffecient to loop over if there is a large user array and it needs to be done on multiple pages.
My current attempt is using a different method like so:
angular.module('haulya.main')
.controller('RegisterController', ['Auth', '$scope', 'User', '$ionicPlatform', '$cordovaCamera','CurrentUserService',
function(Auth, $scope, User, $ionicPlatform, $cordovaCamera, CurrentUserService) {
//scope variable for controller
$scope.user = {};
console.log(User);
$scope.createUser = function(isValid) {
var userModel;
$scope.submitted = true;
//messages for successful or failed user creation
$scope.user.message = null;
$scope.user.error = null;
//if form is filled out valid
if(isValid) {
//Create user with email and password firebase Auth method
Auth.$createUser({
email: $scope.user.email,
password: $scope.user.password
})
.then(function(userData) {
userModel = {
uid: userData.uid,
photo: $scope.user.photo || null,
firstName: $scope.user.firstName,
lastName: $scope.user.lastName,
email: $scope.user.email,
cell: $scope.user.cell,
dob: $scope.user.dob.toString(),
city: $scope.user.city,
state: $scope.user.state,
zip: $scope.user.zip
}
// add new user to profiles array
User.create(userModel).then(function(user) {
$scope.sharedUser = User.get(user.path.o[1]);
});
$scope.user.message = "User created for email: " + $scope.user.email;
})
.catch(function(error) {
//set error messages contextually
if(error.code == 'INVALID_EMAIL') {
$scope.user.error = "Invalid Email";
}
else if(error.code == 'EMAIL_TAKEN'){
$scope.user.error = "Email already in use, if you think this is an error contact an administrator";
}
else {
$scope.user.error = "Fill in all required fields";
}
});
}
};
//Get profile pic from camera, or photo library
$scope.getPhoto = function(type) {
$ionicPlatform.ready(function() {
//options for images quality/type/size/dimensions
var options = {
quality: 65,
destinationType: Camera.DestinationType.DATA_URL,
sourceType: Camera.PictureSourceType[type.toUpperCase()],
allowEdit: true,
encodingType: Camera.EncodingType.JPEG,
targetWidth: 100,
targetHeight: 100,
popoverOptions: CameraPopoverOptions,
saveToPhotoAlbum: false
};
//get image function using cordova-plugin-camera
$cordovaCamera.getPicture(options)
.then(function(photo) {
$scope.user.photo = photo;
}, function(err) {
console.log(err);
});
});
};
}]);
And here's the service the controller is using:
angular
.module('haulya.main')
.factory('User', function($firebaseArray) {
var ref = new Firebase('https://haulya.firebaseio.com');
var users = $firebaseArray(ref.child('profiles'));
var User = {
all: users,
create: function(user) {
return users.$add(user);
},
get: function(userId) {
return $firebaseArray(ref.child('profiles').child(userId));
},
delete: function(user) {
return users.$remove(user);
}
};
return User;
});
This also works, but again I don't have a solid reference to the currently logged in users object data from the array. The objects id is only stored on the controllers scope.
I looked through other posts, but they were all using older versions of firebase with deprecated methods.
If you're storing items that have a "natural key", it is best to store them under that key. For users this would be the uid.
So instead of storing them with $add(), store them with child().set().
create: function(user) {
var userRef = users.$ref().child(user.uid);
userRef.set(user);
return $firebaseObject(userRef);
}
You'll note that I'm using non-AngularFire methods child() and set(). AngularFire is built on top of Firebase's regular JavaScript SDK, so they interoperate nicely. The advantage of this is that you can use all the power of the Firebase JavaScript SDK and only use AngularFire for what it's best at: binding things to Angular's $scope.
Storing user data is explained in Firebase's guide for JavaScript. We store them under their uid there too instead of using push(), which is what $add() calls behind the scenes.