How to denormalize & sync Mongoose Models easily? - database

Suppose we have three model,
CategoryModel:
{ _id, title, slug },
UserModel:
{ _id, name, username, email },
PostModel :
{
_id,
title,
description,
category:{
_id, title, slug
},
creator {
_id, name, username
}
}
If category updated so PostModel's category should be updated.
If userModel's document updates then PostModel's creator should be updated.
How can we synchronize these denormalized feilds

Related

What is the most performant way to do both a DB query and a DB update in a single API call in Next.js with Prisma

I have a API route that returns a blogpost by ID.
I also want to update the viewcount on this blogpost in the same API call that fetches the post.
What is the fastest way to do this? I want to show the blogpost as fast as possible, while also incrementing its counter.
I use Next.js and Prisma ORM with PostgreSQL from supabase.
I was thinking of using Promise.All like this:
export default async function handle(req, res) {
const { id } = req.query;
const blogpost = prisma.post.findUnique({
where: {
id: id
});
const incrementPost = prisma.post.update({
where: {
id: id
},
data: {
views: { increment: 1 }
});
Promise.all([blogpost, incrementPost])
.then((values) => {
res.status(200).json({values[0]})
})
.catch((error) => {
res.status(500)
});
}
Is this a good way to solve this?
User model could be like this:
model User {
id String #id #default(cuid())
title String
body String
views Int #default(0)
}
If you update something in Prisma, it will return the updated item as part of the update operation. From your code, incrementPost and blogpost will both refer to the same post. So you can just return incrementPost from your API.
Documentation: https://www.prisma.io/docs/concepts/components/prisma-client/crud#update-a-single-record
export default async function handle(req, res) {
const {
id
} = req.query;
prisma.post.update({
where: {
id: id
},
data: {
views: {
increment: 1
}
})
.then((updatedpost) => {
res.status(200).json(updatedpost)
});
.catch(() => res.status(500));
})
}
Thanks to Shea for providing the solution that works for me!
I made a ##unique([creatorId, slug]) in my model, and used the new UserWhereUniqueInput in Prisma 4.5.0 to run an update on my Post model.
I only have username and slug to identify the post from my frontend.
let post = await prisma.post.update({
where: {
creatorId_slug: {
creatorId: user.id,
slug: slug,
},
deletedAt: null,
published: {
not: null
}
},
data: {
views: {
increment: 1
}
},
include: {
image: true
}
}
This returns the post and increments the "views" in one go, and solves my problem!
Simplified Model from my real app:
model Post {
id String #id #default(cuid())
published DateTime?
creatorId String
userId Int
user UserInfo #relation("posts", fields: [userId], references: [id], onDelete: Cascade)
title String
body String
slug String
##unique([creatorId,slug])
}

React how to store state value onSubmit

I am trying to store the value of one state property within another on submit, so that I can submit a URL friendly slug to my database.
Below is part of the function that is called when my form is submitted. Currently the form submits to a database (Firestore) and it works fine. However, I need to collect the value that a user inputs into streetAddress, slugify it, and then submit that to my database as its own slug field using the slug property of my state.
The problem I have, is I don't know how to do this. I've tried a few ways and slug is submitted to the database, but always with an empty value. Below is how I have attempted it.
onSubmit = event => {
const { reviewTitle, reviewContent, streetAddress, cityOrTown,
countyOrRegion, postcode, startDate, endDate, landlordOrAgent, rating, slug } = this.state;
this.setState({
slug: streetAddress
})
// Creating batch to submit to multiple Firebase collections in one operation
var batch = this.props.firebase.db.batch();
var propertyRef = this.props.firebase.db.collection("property").doc();
var reviewRef = this.props.firebase.db.collection("reviews").doc();
batch.set(propertyRef, { streetAddress, cityOrTown,
countyOrRegion, postcode, slug,
uid });
batch.set(reviewRef, { startDate, endDate,
reviewTitle, reviewContent, rating,
uid });
batch.commit().then(() => {
this.setState({ ...INITIAL_STATE });
});
event.preventDefault();
};
Can anyone point me in the right direction or tell me what I'm doing wrong?
this.setState is a async function. So what you can do is calling a callback function after the state is updated.
this.setState({
slug: streetAddress
}, () => {
// Creating batch to submit to multiple Firebase collections in one operation
var batch = this.props.firebase.db.batch();
var propertyRef = this.props.firebase.db.collection("property").doc();
var reviewRef = this.props.firebase.db.collection("reviews").doc();
batch.set(propertyRef, {
streetAddress, cityOrTown,
countyOrRegion, postcode, slug,
uid
});
batch.set(reviewRef, {
startDate, endDate,
reviewTitle, reviewContent, rating,
uid
});
batch.commit().then(() => {
this.setState({ ...INITIAL_STATE });
});
event.preventDefault();
})

Data Relationships and Connection Types with Mutations in Prisma, GraphQL, and Apollo

Sorry for the long post, but I tried to be as detailed as possible.
How can I edit my current model, schema, and resolver to be able to save/connect a related type (Vendor) to my created type (Item) via a web form?
I want to create an inventory item and select a vendor to be associated with that item.
I have a Prisma data model like so (I've other fields for simplicity because I have no trouble saving the other fields; it's just with other types where there is a relation)...
Items may or may not have a vendor associated with them. Vendors may or may not have a list of items currently associated with them.
type Item {
id: ID! #id
name: String!
description: String!
vendor: Vendor
}
type Vendor {
id: ID! #id
items: [Item!]
}
I have a graphql schema like this (compare to modified schema at the end of this question)...
type Mutation {
createItem(
name: String!
description: String!
): Item!
}
My resolver:
async createItem(parent, args, ctx, info) {
const item = await ctx.db.mutation.createItem(
{
data: {
...args,
},
}, info);
return item;
}
My React component contains the following:
const CREATE_ITEM_MUTATION = gql`
mutation CREATE_ITEM_MUTATION(
$name: String!
$description: String!
) {
createItem(
name: $name
description: $description
) {
id
name
description
vendor {
id
}
}
}
`;
const ALL_VENDORS_QUERY = gql`
query ALL_VENDORS_QUERY {
vendors {
id
}
}
`;
Later on down the web page in my HTML form, I have:
<Query query={ALL_VENDORS_QUERY}>
{({ data, loading }) => (
<select
id="vendor"
name="vendor"
onChange={this.handleChange}
value={this.state.vendor}
>
<option>Vendor</option>
{data.vendors.map(vendor => (
<option key={vendor.id} value={vendor.id}>{vendor.name}</option>
))}
</select>
)}
</Query>
I just don't know how to connect the vendor to the item through a form submission. I can get it working if I hard code the vendor id in my resolver like so:
async createItem(parent, args, ctx, info) {
const item = await ctx.db.mutation.createItem(
{
data: {
vendor: {
connect: { id: "ck7zmwfoxg4b70934wx8kgwkx" } // <-- need to dynamically populate this based on user input from the form
},
...args,
},
}, info);
return item;
}
...but that obviously is not what I want.
To me, it makes the most sense to modify my schema like so:
createItem(
name: String!
description: String!
vendor: Vendor <--- added
): Item!
But when I do that I get this:
Error: The type of Mutation.createItem(vendor:) must be Input Type but got: Vendor.
How can I edit my current model, schema, and resolver to be able to save/connect a vendor ID that is selected on the form?
The answer to my problem was found here: How to fix 'Variable "$_v0_data" got invalid value' caused from data types relation - Mutation Resolver
I was spreading the args (...args) while also passing the vendor argument.
Here is my updated mutation:
createItem(
name: String!
description: String!
vendorId: ID
): Item!
and its resolver:
async createItem(parent, {name, description, vendorId}, ctx, info) {
const item = await ctx.db.mutation.createItem(
{
data: {
vendor: {
connect: { id: vendorId }
},
name,
description,
},
}, info);
return item;
}
Boom! 💥

Single post query shows "Cannot return null for non-nullable field" error when multiple post query is working fine

My query for a single post shows
Cannot return null for non-nullable field Post.author
error message when I include the author field, both from React as well as the Playground. It's working fine for the posts query which queries for multiple posts and is able to retrieve the specific author, but not when I perform a single post query.
Client-side schema:
posts(query: String, first: Int, skip: Int, after: String, orderBy: UserOrderByInput): [Post!]!
post(id: ID): Post!
Query from React:
(gql is the same for post and posts except for the sorting arguments)
const { data, error, loading } = useQuery(GET_POST, {
variables: {
id: props.match.params.id
}
})
export const GET_POST = gql`
query Post($id: ID!) {
post(
id: $id
){
id
title
body
location
author{
id
firstName
lastName
}
}
}
`
Server-side query:
post(parent, args, { prisma }, info) {
if(!args.id) {
throw new Error("No search query input")
}
return prisma.query.post({
where: {
id: args.id
}, info})
},

GatsbyJS filter queries by location pathname

I'm building a blog with products, each product belongs to several categories. You can click a certain category, and it will take you to a page that only displays products that have that category.
Right now, i'm getting all products on every "category page", and use JS to filter the products, but i want to only load data that i need from the start.
Issue is that the variable that i'm suppost to filter by, is a variable that i compute from location.pathname; (I removed a lot of not relevant code from the snippet)
How can i find a syntax that allows me to add another filter to this query, that uses the "category" variable from this template component?
render() {
const { classes } = this.props
const posts = get(this, 'props.data.allContentfulBlog.edges')
const edges = this.props.data.allContentfulBlog.edges
const category = location.pathname
return (
<div className={classes.container}>
</div>
)
}
query categoryPostQuery {
allContentfulBlog(
filter: { node_locale: { eq: "en-US" } }
sort: { fields: [date], order: DESC }
) {
edges {
node {
id
categories
date(formatString: "DD MMMM, YYYY")
slug
}
}
}
}
I am supposed to enter the categories field, which is an array, and check if it includes the "category" variable.
This can be accomplished using Gatsby's query variables:
query categoryPostQuery($category: String) {
allContentfulBlog(
filter: { node_locale: { eq: "en-US" }, categories: { in: [$category] } }
sort: { fields: [date], order: DESC }
) {
edges {
node {
id
categories
date(formatString: "DD MMMM, YYYY")
slug
}
}
}
}
And the category variable can be set using the context option in gatsby-node.js createPages:
createPage({
path,
component: categoryTemplate,
// If you have a layout component at src/layouts/blog-layout.js
layout: `blog-layout`,
context: {
category: 'games'
},
})

Resources