Field name different from column name in db - node.js

I just started using graphql with mysql, i would like to know if it is possible to use a name in the graphql query different from the column name in my data base.
For example i have a table users with the columns userName and password, when i define the type for the schema i have the following:
const unidadesMedidaInternaType = new GraphQLObjectType({
name: 'unidadesMedidaInterna',
fields: () => ({
userName: { type: GraphQLID },
password: { type:GraphQLString }
})
});
the resolver:
resolve (parent, args) {
return pool.query(`SELECT * FROM users`);
}
so i have to query like this:
{
users {
userName,
password
}
}
i would like to have different names in the query like this:
{
users {
Name,
secret
}
}
i tried changing the names of the fields in the type definition but the result of the query is full of nulls values.

In order to have different names in the queries you have 2 options:
Option 1: Use aliases to run the query:
You can run your query with aliases like
{
users {
Name: userName,
secret: password
}
}
In this case you are just renaming the fields name on execution time, so the original names will still be available to query.
Option 2: Map the query result to the GraphQLObject type.
First rename the fields:
const unidadesMedidaInternaType = new GraphQLObjectType({
name: 'unidadesMedidaInterna',
fields: () => ({
Name: { type: GraphQLID },
secret: { type:GraphQLString }
})
});
Then map the result of the query to match the fields:
resolve (parent, args) {
const result = pool.query(`SELECT * FROM users`);
// If the result of the query is an array then you have to map its items
return { Name: result.userName, secret: result.password }
}

Related

Can't disconnect relation of explicit many-to-many

I'm trying to delete some users which are related to a group.
Here is the schema:
model User {
id String #id #default(cuid())
username String
email String #unique
password String?
group GroupUser[]
}
model Group {
id String #id #default(cuid())
name String
user GroupUser[]
}
model GroupUser{
userId String
user User #relation(fields: [userId],references: [id],onDelete: Cascade,onUpdate:Cascade)
groupId String
group Group #relation(fields: [groupId],references: [id],onDelete: Cascade,onUpdate: Cascade)
##id([userId,groupId])
}
The code to delete the users:
async deleteUsersFromGroup(id: string, userData: UpdateGroupDto): Promise<number> {
const deletedUsers = await prisma.group.update({
where: {
id: id,
},
data: {
user: { disconnect: /* trying to put the array of users id here */ },
},
});
return deletedUsers.length;
}
The problem is that I want to give the userID inside of the disconnect but it is asking me for userId_groupId which is the relational key.
You would need to delete the record from the connecting table i.e. GroupUser.
You can try something like this:
await prisma.groupuser.delete({
where: {
userId_groupId: {
userId: 'userId',
groupId:'groupId'
}
}
});
If the connecting records are deleted then both the entities would be disconnected.
Since I wanted to delete multiple users at the same time I used the map function inside userId, resorting to prisma.groupUser.delete().
async deleteUsersFromGroup(id: string, userData: DeleteGroupDto): Promise<any> {
const response = await prisma.groupUser.deleteMany({
where: {
groupId: id,
userId: { in: userData.users.map(user => user.userId) },
},
});
return response.count
}

Sequelize upsert or create without PK

I'm unable to perform any kind of upsert or create within Sequelize (v: 6.9.0, PostGres dialect).
Using out-of-the-box id as PK, with a unique constraint on the name field. I've disabled timestamps because I don't need them, and upsert was complaining about them. I've tried manually defining the PK id, and allowing Sequelize to magically create it. Here's the current definition:
const schema = {
name: {
unique: true,
allowNull: false,
type: DataTypes.STRING,
}
};
class Pet extends Model { }
Pet.define = () => Pet.init(schema, { sequelize }, { timestamps: false });
Pet.buildCreate = (params) => new Promise((resolve, reject) => {
let options = {
defaults: params
, where: {
name: params.name
}
, returning: true
}
Pet.upsert(options)
.then((instance) => {
resolve(instance);
})
.catch(e => {
// message:'Cannot read property 'createdAt' of undefined'
console.log(`ERROR: ${e.message || e}`);
reject(e);
});
});
module.exports = Pet;
Upsert code:
// handled in separate async method, including here for clarity
sequelize.sync();
// later in code, after db sync
Pet.buildCreate({ name: 'Fido' });
In debugging, the options appear correct:
{
defaults: {
name: 'Fido'
},
returning:true,
where: {
name: 'Fido'
}
}
I've also tried findOrCreate and findCreateFind, they all return errors with variations of Cannot convert undefined or null to object.
I've tried including id: null with the params, exact same results.
The only way I've succeeded is by providing PK in the params, but that is clearly not scalable.
How can I upsert a Model instance without providing a PK id in params?
class Pet extends Model { }
//...you might have the id for the pet from other sources..call it petId
const aPet = Pet.findCreateFind({where: {id: petId}});
aPet.attribute1 = 'xyz';
aPet.attribute2 = 42;
aPet.save();

create "dynamic query" in nodejs with sequelize?

I was wondering if it was possible to make a "dynamic condition" with sequelize using findOne/findOrCreate etc instead of row SQL query
i use it with graphQL and arguments could be optionnal in graphQL, so what i want to do is :
if i have id and name as argument :
user.findOrCreate({
where: {
id: args.id,
name : args.name,
}
})
and if i have id, name and email :
user.findOrCreate({
where: {
id: args.id,
name : args.name,
email: args.email,
}
})
so it is possible to have a parameter inside the findOrCreate to check if something exist(here, args.email) and if not, doesn't include it inside the query ?
You can create the the function and import that function:
export const findOrCreate = async function (query) {
try {
const user = await user.findOrCreate(query);
return user;
} catch (e) {
throw Error('Error occur while finding or creating the records’);
}
};
wherever you need import that function and call with the required parameters
const query = {
where: { // we search for this user
id: args.id,
name : args.name,
email: args.email,
},
defaults: {
job: 'Technical Lead JavaScript'
} // if it doesn't exist, we create it with this additional data
}
await findOrCreate(query )

How to add dynamic additional attributes into a junction table with ManyToMany association in Sequelize

I created a many-to-many association by sequelize in my koa app. But I had no idea on how to create additional attributes in the junction table. Thanks.
I referred to the official doc of sequelize but didn't find a solution. In brief:
"an order can have many items"
"an item can exist in many orders"
Then I created OrderItems as junction table.
But I have trouble in inserting value into the junction table
// definitions
const Item = sequelize.define('item', itemSchema);
const Order = sequelize.define('order', orderSchema);
// junction table
const OrderItems = sequelize.define('order_item', {
item_quantity: { type: Sequelize.INTEGER } // number of a certain item in a certain order.
});
// association
Item.belongsToMany(Order, { through: OrderItems, foreignKey: 'item_id' });
Order.belongsToMany(Item, { through: OrderItems, foreignKey: 'order_id' });
// insert value
const itemVals = [{ name: 'foo', price: 6 }, { name: 'bar', price: 7 }];
const orderVals = [
{
date: '2019-01-06',
items: [{ name: 'foo', item_quantity: 12 }]
},
{
date: '2019-01-07',
items: [{ name: 'foo', item_quantity: 14 }]
}
]
items = Item.bulkCreate(itemVals)
orders = Order.bulkCreate(orderVals)
//Questions here: create entries in junction table
for (let order of orders) {
const itemsInOrder = Item.findAll({
where: {
name: {
[Op.in]: order.items.map(item => item.name)
}
}
})
order.addItems(itemsInOrder, {
through: {
item_quantity: 'How to solve here?'
}
})
}
// my current manual solution:
// need to configure column names in junction table manually.
// Just like what we do in native SQL.
const junctionValList =[]
for (let orderVal of orderVals) {
orderVal.id = (await Order.findOne(/* get order id */)).dataValues.id
for (let itemVal of orderVal.items) {
itemVal.id = (await Item.findOne(/* get item id similarly */)).dataValues.id
const entyInJunctionTable = {
item_id: itemVal.id,
order_id: orderVal.id,
item_quantity: itemVal.item_quantity
}
junctionValList.push(entyInJunctionTable)
}
}
OrderItems.bulkCreate(junctionValList).then(/* */).catch(/* */)
In case that this script it's for seeding purpose you can do something like this:
/*
Create an array in which all promises will be stored.
We use it like this because async/await are not allowed inside of 'for', 'map' etc.
*/
const promises = orderVals.map((orderVal) => {
// 1. Create the order
return Order.create({ date: orderVal.date, /* + other properties */ }).then((order) => {
// 2. For each item mentioned in 'orderVal.items'...
return orderVal.items.map((orderedItem) => {
// ...get the DB instance
return Item.findOne({ where: { name: orderedItem.name } }).then((item) => {
// 3. Associate it with current order
return order.addItem(item.id, { through: { item_quantity: orderedItem.item_quantity } });
});
});
});
});
await Promise.all(promises);
But it's not an efficient way to do it in general. First of all, there are a lot of nested functions. But the biggest problem is that you associate items with the orders, based on their name and it's possible that in the future you will have multiple items with the same name.
You should try to use an item id, this way you will be sure about the outcome and also the script it will be much shorter.

GraphQL not loading children lists

I had started out writing verbose GraphQL and switched to graphql-tools and makeExecutableSchema and with the changes it will load queries for user(id: "N"), users, group(id: "N") and groups, however, the nested lists just return "id": null. I feel like I have to have a small mistake somewhere but am not seeing it:
const { makeExecutableSchema } = require('graphql-tools')
const db = require('./db')
const typeDefs = `
type User {
id: String
first_name: String!
last_name: String!
email: String!
friends: [User!]
groups: [Group!]
}
type Group {
id: String
name: String!
}
type Query {
users: [User!]!
user(id: String!): User
groups: [Group!]!
group(id: String!): Group
}
`
const resolvers = {
Query: {
users: db.readAllUsers,
user: (root, args, { loaders }) => loaders.user.load(args.id),
groups: db.readAllGroups,
group: (root, args, { loaders }) => loaders.group.load(args.id)
}
}
module.exports = makeExecutableSchema({ typeDefs, resolvers })
Any help would be greatly appreciated!
Edit: to clarify, here's what the data source looks like
You've defined resolvers for your queries, while are just fields on your Query type. However, you don't have any resolvers for the group field on the User type, so GraphQL falls back to using the default resolver for that field. Since the property field on the Object User resolves to is just an array of ids, GraphQL doesn't know how to make sense of it -- after all, you told it groups would be an array of objects.
You'll need to add a resolver for the groups field and transform that array into a Promise that will resolve to an array of Group objects:
const resolvers = {
Query: {
users: db.readAllUsers,
user: (root, args, { loaders }) => loaders.user.load(args.id),
groups: db.readAllGroups,
group: (root, args, { loaders }) => loaders.group.load(args.id)
},
User: {
groups: ({ groups }, args, { loaders }) => {
return Promise.all(groups.map(id => loaders.group.load(id)))
}
}
}

Resources