RESTful API complex queries - mysql

I have a simple restful api for destinations. It's built using Express and sequelize with a MySQL database.
It has Cities, Countries and Regions. Cities belongTo Countries which belongsTo Regions.
Now, it's usual that I have to do complex find operations. For example:
GET /cities
{
where: {
name: {'$like': 'bue%'}
},
include: [{model: Country, where: {region_id: 1}}]
}
With this I am trying to fetch all cities where the name starts with 'bue', but only whose country's region_id is 1.
This could be taken one step forward if I specify which fields I want returned. The models have some text fields which I might not need, so the above query becomes:
{
where: {
name: {'$like': 'bue%'}
},
attributes: ['id', 'name'],
include: [{
model: Country,
attributes: ['id', 'name'],
where: {region_id: 1}
}]
}
This is already quite complex. Image if I want to include the Country's region and add some filter there as well.
So, my question is: how do I go about passing this complex query?
Right now I am using url params with encoded json. So I have something like:
where: {name: {'$like': 'bue%'}} => Becomes:
/cities?where=%7Bname%3A%20%7B%27%24like%27%3A%20%27bue%25%27%7D%7D
You can see that this results in a damn ugly URL that is quite hard to work with. If my query is complex, the URL becomes a monster. If my where param as OR / AND and complex conditions, it gets really big and messy.
Anyway, that's the question. Should I keep things as they are. Is there any more proper way to do this somehow.
Some example would be appreciated.

One sort of "standard" solution is to POST the complex query (with the query in the HTTP body), let the server store it somewhere and return a new URL that references the stored query. After that clients can issue a GET on the new query URL to get the result.

Related

Sequelize select statement for this example with express get method

I have these 2 words:
The words in mysql db
And I have this category:
The category
As we can see in the word-table, the words have also a categoryId attribute.
I've made an junctiontable in case I need it but it's currently empty, it looks like that:
junction table
I want to display a list in my ui that is a admin-panel that would look like this for example:
Words
Category
God
Religion
Ford Mustang
Cars
Every word has exactly one category.
To do that I want to use axios to fetch a get request of my node.js server.
What would look the get method with the corresponding sequelize query to get the data and maybe also the method in the react ui?
I'm trying to figure it out for about two days now and I can't get out of this shithole...
What is the project*:**
Full stack web app that simulates the game "wheel of fortune". Contains a auth-system where admins can also make CRUD options for words, questions and create categories that belong to a word or question. Classic users can only play the game.
Technologies:
React -> Frontend
Node.js Backend -> Express.js and Sequelize
Database: MySQL with mysql workbench
Assuming model name for Words table is Word and Category for category table the code to query all the data would be such:
const words = await Word.findAll({
where: {
// here you can do filtering if needed
},
include: [
{
model: Category,
attributes: [
'category',
],
as: 'categories',
},
],
});
This will return list of words and associated categories. Then you would just create api endpoint and return queried data and display on frontend.

Can a REST API returns properties values that does not exists on database?

Imagine that I have on my DB one table called 'match' and I store:
id
round_id
score
start_date
end_date
When my REST API returns an JSON on endpoint /matches i must obligatorily return only the fields/columns that exists in the DB or I can return some custom fields like this:
{id: 1, is_over: true, no_goals: false}
Also, this table match has only relationship with the round table, and the round table has an relationship with season that has relationship with the competition table.
In the /matchs endpoints json, can I return competition data direclty ? Something like this:
/matchs:
{id: 1, is_over: true, no_goals: false, competition: { id: 2, name: 'foo',...}}
It's your API. You can do whatever you want with it!
When you work with a REST API, you work with data.
In this case, you still work with data, you just add new fields which are not in you database.
So it's possible, it's OK to do so, you don't break the REST API model. One thing a lot of people do is to inject new field about your request, like a custom http code, or a custom message.
And for competition, you can do it this way
So all good !

How would you model a collection of users and friends in Firebase?

I'm trying to create a database (json) with Firebase.
I searched the docs and the net but couldn't find a clear way to start.
I want to have a database of users.
each user (represented as UID) should have a nickname and a list of friends.
I tried making a .json file that looks like this:
{
users:{
}
}
and adding it to the Firebase console to get started but it wouldn't work.
How can I do it?
the database should look like this:
{
users:{
UID:{
nickname: hello
friends: UID2
}
UID2:{
nickname: world
friends: UID
}
}
I don't know if I got that right, so I would really appreciate any help you guys could give me at this subject.
Thanks in advance!
Seems like a good place to start. I would make two changes though.
keep the list is friends separate
keep the friends as a set, instead of a single value or array
keep the list is friends separate
A basic recommendation when using the Firebase Database is to keep your data structure shallow/flat. There are many reasons for this, and you have at least two.
With your current data structure, say that you want to show a list of user names. You can only get that list by listening to /users. And that means you don't just get the user name for each user, but also their list of friends. Chances that you're going to show all that data to the user are minimal, so that means that you've just wasted some of their bandwidth.
Say that you want to allow everyone to read the list of user names. But you only want each user to be able to read their own list of friends. Your current data structure makes that hard, since permission cascades and rules are not filters.
A better structure is to keep the list of user profiles (currently just their name) separate from the list of friends for each user.
keep the friends as a set
You current have just a single value for the friends property. As you start building the app you will need to store multiple friends. The most common is to then store an array or list of UIDS:
[ UID1, UID2, UID3 ]
Or
{
"-K.......1": "UID1"
"-K.......5": "UID2"
"-K.......9": "UID3"
}
These are unfortunately the wrong type for this data structure. Both the array and the second collection are lists: an ordered collection of (potentially) non-unique values. But a collection of friends doesn't have to be ordered, it has to be unique. I'm either in the collection or I'm not in there, I can't be in there multiple times and the order typically doesn't matter. That's why you often end up looking for friends.contains("UID1") or ref.orderByValue().equalTo("UID1") operations with the above models.
A much better model is to store the data as a set. A set is a collection of unordered values, which have to be unique. Perfect for a collection of friends. To store that in Firebase, we use the UID as the key of the collection. And since we can't store a key without a value, we use true as the dummy value.
So this leads to this data model:
{
users:{
UID:{
nickname: hello
}
UID2:{
nickname: world
}
}
friends:{
UID:{
UID2: true
}
UID2:{
UID: true
}
}
}
There is a lot more to say/learn about NoSQL data modeling in general and Firebase specifically. To learn about that, I recommend reading NoSQL data modeling and watching Firebase for SQL developers.
I keep a collection of Friends where the users field is an array of 2 user ids: ['user1', 'user2'].
Getting the friends of a user is easy:
friendsCollection.where("users", "array-contains", "user1").get()
This should get you all documents where user1 appears.
Now the tricky part was on how to query a single friend. Ideally, firebase would support multiple values in array-contains, but they won't do that: https://github.com/firebase/firebase-js-sdk/issues/1169
So they way I get around this is to normalize the users list before adding the document. Basically I'm utilizing JS' truthiness to check what userId is greater, and which is smaller, and then making a list in that order.
when adding a friend:
const user1 = sentBy > sentTo ? sentBy : sentTo
const user2 = sentBy > sentTo ? sentTo : sentBy
const friends = { users: [user1, user2] }
await friendsCollection.add(friends)
This basically ensures that whoever is part of the friendship will always be listed in the same order, so when querying, you can just:
await friendsCollection.where("users", "==", [user1, user2]).get()
This obviously only works because I trust the list will always have 2 items, and trust that the JS truthiness will work deterministically, but it's a great solution for this specific problem.

Firebase EqualTo performance & security

I have an array of objects related to users and want to get all objects related to one user. I can't save the userid as a parent node but as a child so that I want to use the equalTo method.
ref.orderByChild("userid").equalTo(uid).on("child_added", function(snapshot) {
console.log(snapshot.val());
});
Does this first query all objects (slow) and then select only the required ones or does firebase optimize the query itself on the server? I come from SQL and I am a bit unsure how to handle where queries in firebase.
Edit: there are also security issues. A user could receive all objects by hacking the js code? I hope the security rules should solve this?
Example JSON:
{
Objectkey1: { userid: 'uid', ... },
Objectkey2: { userid: 'uid', ... },
...
}
Does this first query all objects (slow) and then select only the required ones or does firebase optimize the query itself on the server?
Yup, that's pretty much what happens. So this operation will always get slower as you add more items to the location identified by ref.
If that type of performance is a concern (i.e. if you care about scalability), consider adding an inverted/secondary index to the thing that user identified by uid.

Minimizing server trips in setting default value in 1:n relationship in lightswitch html5 client

I have lightswitch entities created in the HTML5 client. There is a field that the user should not be setting that needs to be set based on their login. In this case, the client the user is associated with.
The standard examples (in Michael Washington's book Creating HTML 5 Websites ... Using Lightswitch and all over the web) involve assigning the user name as the relevant field, setting up a little handler on the server to return the relevant field using an AJAX call.
This is was all well and good while prototyping, but now that we are doing this for real, there is a relationship to another entity involved, the Client Entity, so you can't just assign the Client Id. So instead of simply assigning a Client ID, now we have associate a whole Client Object to the entity.
Here the suggestion is to do what ends up looking like this:
myapp.activeDataWorkspace.ApplicationData.Clients_SingleOrDefault(1).execute().then(function (ClientQuery) {
entity.setClient(ClientQuery.results[0]);
});
My problem is with this part:
Clients_SingleOrDefault(1)
I need to get that number dynamically, not just a hard coded 1, as that OP suggested. So I can do it in two server calls, one to get the ID, and then the next one to substitute that result into the next call, but that seems ... inefficient.
msls.promiseOperation(CallGetClientId).then(function PromiseSuccess(PromiseResult) {
myapp.activeDataWorkspace.ApplicationData.Clients_SingleOrDefault(Number(PromiseResult)).execute().then(function (ClientQuery) {
entity.setClient(ClientQuery.results[0]);
});
});
function CallGetClientId(operation) {
$.ajax({
type: 'post',
data: {},
url: '../UserCode/GetClientId.ashx',
success: operation.code(function AjaxSuccess(AjaxResult) {
operation.complete(AjaxResult);
})
});
}
It would seem that there should be a better way to do it. Is there?
The simplest method of achieving this with one server call is to implement a scalar query.
For example, if you create a scalar query called ClientForCurrentUser against your Clients table you'd be able to make a single client side call as follows:
myapp.activeDataWorkspace.ApplicationData.ClientForCurrentUser().execute().then(function (query) {
if (query && query.results && query.results.length !== 0) {
entity.setClient(query.results[0]);
}
});
As regards configuring the scalar query, the following post covers an example (note the setting of the 'Number of Results Returned' property):
Applying where clause to child collection in LightSwitch query
Then, assuming you have a field against your Client record that relates it to the current username (e.g. Client.RelatedUser) you'd implement the following type of PreprocessQuery method against the scalar query:
partial void ClientForCurrentUserPersonForCurrentUser_PreprocessQuery(ref IQueryable<Client> query)
{
var username = this.Application.User.Name;
query = query.Where(c => c.RelatedUser.Equals(username, StringComparison.OrdinalIgnoreCase)).Take(1);
}