Mongoose/NodeJS - Sort button in .EJS template - node.js

In my Mongoose controller I am sorting according to creation date of the product:
let products = await Product.find().sort({ 'createdAt': -1 });
I want to create a button in EJS template which allows the user to select different criteria to sort by (e.g. price). Creating the button itself is no problem, but I don't know how to access the sort function in the controller when an option is selected (e.g. if price selected, sort by price), and where I should be putting the code, in the controller or in the EJS template?
I assume I should be using req.query, but I don't know how. Thanks.

Part One - Abstraction Mongoose API to JS
Mongoose allows you to build your query in sections. Let say you have a function the query Product with additional options object.
findOptions - contains the filter options (the where part of the query)
pagingOptions - contains sorting+ordering,, page and pageSize.
This is all the context you need to build a query.
async function getProducts(findOptions, pagingOptions) {
const query = Product.find(findOptions);
if(pagingOptions.sort) {
// Note: This example assumes that paging.sort has the same structure as Monguse sort object.
query.sort(paging.sort);
}
let products = await query.exec();
...
return products;
}
Usage:
let products = await getProducts({}, { 'createdAt': -1 });
let products = await getProducts({ 'createdAt': { $gt: Date.now() }}, { '_id': 1 });
You can add or restrict many many things using the options object to manage the query context
See: mongoose query doc
Part Two - Exposing an API
Now that we have a method at the server-side that can query the Products using only JS object (JSON) you will need to expose an HTTP API so a client will be able to call the server with his query criteria.
Let defined this is the HTTP API that we will send to the client to integrate with:
POST /products
// request body
{
sort: { FIELD_NAME: ASC|DESC }, // ASC=1, DESC=-1
}
Usage:
curl -X POST -H "Content-Type: application/json" --data '{ sort: { "createdAt": "1" } }' http:localhost:3000
Note: you can extend both the HTTP API and the Backend method options to extend the functionality further.
See the following to check how to implement HTTP endpoint via express
https://expressjs.com/en/guide/routing.html
http://expressjs.com/en/resources/middleware/body-parser.html#bodyparserjsonoptions
Part Three - FrontEnd Integration
Now all is left is to implement an HTTP client and expose the HTTP API so the rest of the FrontEnd modules will be able to call the backend.
The following is a simple fetch example but you can use any HTTP client lib that you like.
const productsEndpoint = baseUrl + '/products';
async function getProducts(options) {
const response = await fetch(productsEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(options)
});
return response.json();
}
These three steps will you to have a well-defined API across components and the layers within them.

Related

Hubspot pagination using after in nodejs

i am building hubspot api, i having trouble paginating the contacts records.
i am using #hubspot/api-client - npm for integration with hubspot and this is the docs for that https://github.com/HubSpot/hubspot-api-nodejs
hubspotClient.crm.contacts.basicApi
.getPage(limit, after, properties, propertiesWithHistory, associations, archived)
.then((results) => {
console.log(results)
})
.catch((err) => {
console.error(err)
})
in this code there is after argument, we can provide contacts id in it, and it will provide the records including and after that particular id.
How do i use this for pagination. or there is any other way.
Take a look at API Endpoints documentation for GET /crm/v3/objects/contacts and the data you receive. The getPage method returns the following data:
{
"results": [
{
// contact detail here
}
],
"paging": {
"next": {
"after": "NTI1Cg%3D%3D",
"link": "?after=NTI1Cg%3D%3D"
}
}
}
The pagination information is available in paging.next.after (if there is a consecutive page). So you can do something like this to iterate through each page:
async function doSomethingWithEachPage() {
let after = undefined;
const limit = 10;
const properties = undefined;
const propertiesWithHistory = undefined;
const associations = undefined;
const archived = false;
do {
const response = await hubspotClient.crm.contacts.basicApi.getPage(
limit,
after,
properties,
propertiesWithHistory,
associations,
archived
);
// do something with results
console.log(response.results); // contacts list
// pick after from response and store it outside of current scope
after = response.paging?.next?.after;
} while (after);
}
I rewrote the sample code to use async/await so it better works with do...while loop and omitted error handling.
If you are dealing with reasonable small amount of data and have enough of memory, you can also skip the pagination and use the getAll method to load all the data. (In fact, this method does internally a loop similar to the one above.)

Fetch only the document ids for documents that match a query in Firebase Cloud Functions [duplicate]

I have a collection of documents with generated identifiers. The question is: is it possible to get a list of identifiers without querying all documents data? Is it better to store these keys in a separate collection?
The answer depends on which API you're trying to use.
For mobile/web SDKs there is no way to do what you're asking for since these clients do not support projections of any kind.
For server SDKs you can do an empty projection, i.e.
db.collection('foo').select()
In this case the server will send you the documents that match, but will omit all fields from the query result.
For the REST API you can do the equivalent with a runQuery that includes a field mask of '__name__', like so:
curl -vsH 'Content-Type: application/json' \
--data '{
"parent": "projects/my-project/databases/(default)",
"structuredQuery":{
"from": [{"collectionId": "my-collection"}],
"select": {
"fields": [{"fieldPath":"__name__"}]
}
}
}' \
'https://firestore.googleapis.com/v1beta1/projects/my-project/databases/(default)/documents:runQuery'
Substitute my-project and my-collection as appropriate. Note that the "collectionId" in the "from" is only the right most name component. If you want keys in a subcollection the REST API wants the parent document name in the "parent" field.
On node.js runtime you can get the list of document IDs like this
const documentReferences = await admin.firestore()
.collection('someCollection')
.listDocuments()
const documentIds = documentReferences.map(it => it.id)
I had a need to fetch only ids without pulling fields of all documents in Python.
I used google-cloud-firestore 2.6.0 package.
select and field_paths keywords were the very important part of this query. They allowed me to process without downloading all documents.
from google.cloud import firestore_v1
db = firestore_v1.Client()
items = db.collection("your_collection_name").select(field_paths=[]).get()
ids = [item.id for item in items]
print(ids)
Here is some JavaScript and a little React code that seems to be working for me with the V1 REST API's runQuery using client SDK bearer tokens in the browser (Chrome). This is patterned off of Gil Gilbert's answer. However, note that parent does not appear in the body by the structured query, and unlike some other answers on Stack Overflow, there is no API key necessary.
const [token, setToken] = useState("");
useEffect(() => {
if (!token) firebase.auth().currentUser.getIdToken(true).then(setToken);
}, [token]);
const getCollectionAsync = useCallback(async collection => {
try {
if (!token) return [];
const parent = `projects/${projectId}/databases/(default)/documents`;
const url = `https://firestore.googleapis.com/v1/${parent}:runQuery`;
const Authorization = `Bearer ${token}`;
const headers = {Authorization, "Content-Type": "application/json"};
const body = {structuredQuery: {from: [{collectionId: collection}],
select: {fields: [{"fieldPath": "__name__"}]}}};
const response = await fetch(url,
{method: "POST", headers, body: JSON.stringify(body)});
const json = await response?.json?.();
return json;
} catch (error) {
console.error(error);
return [];
}
}, [cache, token]);

Adding query param to mailchimp request with Node.js client library

I am trying to list out all my interests from the MailChimp api using the #mailchimp/mailchimp_marketing npm library, as that is what they use as examples for node.js in their docs.
Link to the npm library:
https://www.npmjs.com/package/#mailchimp/mailchimp_marketing
Link to the relevant documentation for this specific endpoint: https://mailchimp.com/developer/api/marketing/interests/list-interests-in-category/
Now, I can get my interests just fine with the example code there:
const run = async () => {
const response = await client.lists.listInterestCategoryInterests(
"list_id",
"interest_category_id"
);
console.log(response);
};
run();
The problem is, that by default the MailChimp API only returns the first 10 items in the list, and I need 15. There is an easy way to change this, of course, by adding a query param count=15. However, I can't find any way to pass on a query param with the listInterestCategoryInterests method as provided through the official library.
!TL;DR! So my question is:
Does anybody know how to pass on query params to the mailchimp API through the official node.js npm library, or do I really have to resort to just dropping the library entirely as it does not provide this basic functionality?
You need to list params as a string in an array:
const response = await client.lists.listInterestCategoryInterests({fields:[ "list_id,interest_category_id"]}
);
NOTE: A prefix maybe required as per below:
const response = await mailchimp.reports.getAllCampaignReports({fields:["reports.campaign_title,reports.clicks"]})
Result:
[0] {
[0] reports: [
[0] { campaign_title: 'COACT EMAIL CAMPAIGN', clicks: [Object] },
[0] { campaign_title: '', clicks: [Object] }
[0] ]
[0] }
const response = await mailchimp.lists.getListMembersInfo(listId,
{
count: 1000
});
For everyone coming here hoping to learn how to pass QUERY params into mailchimp marketing library methods:
The query parameters are taken from opts object - the object properties have to be camelCase.
In terms of which parameter for the method the opts object is - it depends on the method and you might need to check the method's source code, but probably second or third parameter.
As for the question for the concrete method, this should be the solution:
await client.lists.listInterestCategoryInterests(
"list_id",
"interest_category_id",
{ count: 15 }
);

Can we update multiple records with a single PUT request in Node JS?

I am trying to update multiple records through a single put request using Angular HTTP service, which in turn is consuming a Node JS Express API that handles a PUT request. But so far the examples I have seen on the internet a referring to update a single record through a put request. But instead I want to pass an array of objects into the Put request from Angular Http service and it should be able to read that collection in Node JS API. So far I have been passing one single object as a part of request and I could read it's properties via "req.body.propertyname". Can it read the whole array which i want to pass ?
Let's say this is my code on Angular side to update a single book through a put request as below :
updateBook(updatedBook: Book): Observable {
return this.http.put(/api/books/${updatedBook.bookID}, updatedBook, {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
});
}
On Node js front it is able to read the passed book object from client(Angular ) side like below:
.put(function(req, res) {
var data = getBookData();
var matchingBooks = data.filter(function(item) {
return item.bookID == req.params.id;
});
if(matchingBooks.length === 0) {
res.sendStatus(404);
} else {
var bookToUpdate = matchingBooks[0];
bookToUpdate.title = req.body.title;
bookToUpdate.author = req.body.author;
bookToUpdate.publicationYear = req.body.publicationYear;
saveBookData(data);
res.sendStatus(204);
}
});
My question is if I could pass the collection of books at once so that all of them gets updated with a single request ?
updateBook(updatedBooks: Book[]): Observable {
return this.http.put(/api/books, updatedBooks, {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
});
}
If yes then how Node JS could even read this Array passed from client. Will req.body contain this array passed ?
Yes, since we are passing data using the PUT method. In your case, you can send the data to be updated as an array of objects. If you are using MongoDB as your database, you can update data using Update Array Operator.
My suggestion:
The thing is, there is nothing much to be done in the front end. If you need to update an only single record, then pass the single data as an object inside the array. If you want to update multiple records, pass every record as an object inside the array to the backend.
In the backend side, we can receive the data using req.body.books (The name of the object you pass from the frontend). If you are using mongoDB, you can refer to the link on how to store data in an array.
Im also new to node js but i tried to update multiple values through postman and i hit the '/api/books' , the below code worked for me and updated all the values at once in my DataBase using PUT call . Used Mongo DB and Node JS
app.put('/api/books',(req,res)=>{
var updatedData="";
var headersAgain=false;
for(let i =0;i<req.body.length;i++){
DataBaseName.findByIdAndUpdate(req.body[i]._id)
.then(val=>{
val.url=req.body[i].title
val.position=req.body[i].author
val.auto_scroll=req.body[i].publicationYear
updatedData=val+updatedData
val.save((err,updatedObject)=>{
console.log('inside save.....',updatedData)
if(err){
return res.status(500).send(err)
}else{
if(!headersAgain==true){
headersAgain=true
return res.status(201).send(updatedData)
}
}
})
})
}
})

How can I put an Express REST layer on top of an Apollo GraphQL server?

I'm looking into putting a REST layer (using Express) on top of a GraphQL server (Apollo Server v2) to support some legacy apps. To share as much logic as possible, the REST endpoint should ideally wrap a GraphQL query that I'm sending to the GraphQL server, and be able to do small modifications to the response before sending the response to the client.
I'm having trouble figuring out the best way to query the apollo server from the Express routing middleware. So far I've explored two different solutions:
Modify the request from the REST endpoint such that req.body is a valid graphql query, change the req.url to /graphql, and call next(). The problem with this is that I cannot modify the result before it's being sent to the client, which I need to do.
Calling the /graphql endpoint with axios from the routing middleware, and modify the response before sending to the client. This works, but feels to me a bit hacky.
Do you have other suggestions, or maybe even an example?
I believe the solution 2 is okay to implement.
I've made a similar implementation, but in my case, a GraphQL service fetches data from another(multiple) GraphQL service(s).
And somewhere down the line I did something like this:
export type serviceConnectionType = {
endpoint: string
queryType: {
query: Object // gql Object Query
variables: {
input: Object // query arguments (can be null)
}
}
}
export async function connectService(params: serviceConnectionType) {
const response = await fetch(params.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params.queryType),
})
if (response.status === 404) {
console.warn('404 not found')
}
return response.json()
}

Resources