In this chapter, you will add a voting feature that lets your users upvote certain links. Along the way, you will learn about representing many-to-many relationships using Prisma. Then you will learn about custom scalar types and how to add a DateTime
scalar to your GraphQL API. Finally you are going to use the DateTime
scalar to add a new createdAt
field to the Link
type.
You will start with the voting feature.
The first step here is to extend your Prisma data model to represent votes in the database.
Let’s go through the numbered comments to understand the changes:
// 1
: The Link
model has a new voters
relation field which is connected to multiple User
records. Similarly, the User
model has a new votes
relation field which is connected to multple Link
records. This kind of relation is called many-to-many in relational database terminology. To represent such a relation in the database, a separate table is needed, which is often called the join table or relation table. However, Prisma abstracts this away for you and manages this table under the hood (without you having to worry about it), so it’s not visible in the Prisma schema. // 2
: You can see there’s a new attribute in the relation annotation called name
. Notice that now there is more than one relation between the User
and Link
model (A one-to-many PostedBy
relation and a many-to-many Votes
relation). As a result, the name
attribute needs to be specified in the relation annotation so that Prisma can identify the relation a field is referencing. Note: The many-to-many relation where Prisma manages the relation table is called an implicit many-to-many relation. Alternatively, you can choose to define the relation table inside your Prisma schema explicitly. This is called an explicit many-to-many relation. Your use-case will determine which of the two you should use. When you don’t need to attach additional information to the relation (as is the case for your vote relation), it’s easier to use an implicit many-to-many relation. In cases where there is additional data associated with the relation itself, you will need to use an explicit many-to-many relation. You can optionally read more about this in the Prisma documentation.
Just like before, the Prisma Client API will automatically be updated to reflect the changes to your schema.
vote
mutationNow that your database model has been updated, it’s time to implement the vote
mutation, which any logged in user can call to cast a vote for a particular link. Note that this application will only support upvotes, and only logged in users will be allowed to vote on a link.
You will start by creating a Vote.ts
file.
Before creating the vote mutation you will also need to define its return type.
This code follows the same pattern you have used in the last few chapters. Let’s take a look:
// 1
: The Vote
type is a union of two things: the link in question and the user who just cast the vote. // 2
: The vote
mutation will return an instance of Vote
type. The caller will also provide the linkId
argument which identifies the link in question. The userId
does not need to be provided as an argument because it can be decoded from the Authentication header.To incorporate these changes to your GraphQL schema, there’s something else you need to do.
This is what the updated GraphQL schema will look like:
type Mutation {
login(email: String!, password: String!): AuthPayload!
post(description: String!, url: String!): Link!
signup(email: String!, name: String!, password: String!): AuthPayload!
vote(linkId: Int!): Vote
}
type Vote {
link: Link!
user: User!
}
Let’s understand what’s happening in the resolver:
// 1
: If the user provides a valid JWT token, then the userId
variable will be available in the context
argument. This check prevents users that are not logged in from trying to vote.// 2
: The voters
field for the link needs to be updated with a new user. The update query has two parts: the where
option specifies which link to update, and the data
option specifies the update payload. In this case, we simply want to attach a new user to the many-to-many relation represented by the voters
field. This can be done using the connect
option.// 3
: The resolver will return an object of Vote
type, which contains the user and link in question. The typecasting (user as User
) is necessary as the type returned by prisma.user.findUnique
is User | null
, whereas the type expected from the resolve function is User
. Now you will update the definition of the Link
type in your GraphQL schema to add the voters
field.
// 1
: The definition of the voters
field in the GraphQL schema is similar to that of the Prisma data model you updated earlier. The syntax of the resolve
function is also very similar to the previous resolve
function written for the postedBy
field. You also need to update the User
type.
All right, that’s it! Now let’s test the implementation of the vote
mutation! When executing the following instruction, make sure the Authorization
header is enabled and has a valid token.
You should see an output similar to the following:
{
"data": {
"vote": {
"link": {
"url": "nexusjs.org",
"description": "Code-First GraphQL schemas for JavaScript/TypeScript"
},
"user": {
"name": "alice",
"email": "alice@prisma.io"
}
}
}
}
Awesome! Now it’s time to learn about GraphQL scalars.
As mentioned before, scalars are the basic types in a GraphQL schema, similar to the primitive types in programming languages. While doing this tutorial, you have used a few of the built-in scalar types, notably String
and Int
.
While the built-in scalars support most of the common use-cases, your application might need support for other custom scalars. For example, currently in our application, the Link.url
field has a String
scalar type. However, you might want to create an Url
scalar to add custom validation logic and reject invalid urls. The benefit of scalars is that they simultaneously define the representation and validation for the primitive data types in your API.
Nexus allows you to create custom scalars for your GraphQL schema using a standardized interface. To do this, you will need to provide the functions that serialize and deserialize data for that scalar. It’s important to note that under the hood Nexus uses the GraphQLScalarType
class from the graphql
library. Most applications in the node ecosystem uses this class when defining custom scalars. As a result scalars defined across different libraries in the node ecosystem are often compatible with each other.
The graphql-scalars
library provides a large set of custom scalars for creating precise type-safe GraphQL schemas. You are going to import and use the DateTime
scalar from this library in your GraphQL API.
DateTime
scalarStart by adding the graphql-scalars
library to your application.
You will create a new subfolder inside the graphql
folder for your custom scalars.
Now let’s add a new DateTime scalar to your API.
Let’s understand what you did here:
// 1
: The GraphQLDateTime
is a pre-built custom scalar from the graphql-scalars
library. It uses the ISO-8601 specification, which is also used by Prisma for its own DateTime
type. // 2
: The asNexusMethod
allows you to expose a custom scalar as a Nexus type. It takes two arguments: A custom scalar and the name for the Nexus type. To enable the new DateTime
scalar, you will need to pass it to your Nexus makeSchema
call. This can easily be done at src/graphql/index.ts
.
Once your server restarts, you should see a new type in schema.graphql
:
""""
A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.
""""
scalar DateTime
Alright! It’s time to put this new scalar to use in the Link
type. Note that you usually update your database model before making changes to the GraphQL type definition. However the createdAt
field already exists in the Link
Prisma model.
// 1
: The dateTime
field should automatically be available when defining any type using Nexus, thanks to the asNexusMethod
call. This field will get resolved automatically during queries as the Link
model inside Prisma already has a createdAt
field. The updated Link
type definition should be as follows:
type Link {
createdAt: DateTime!
description: String!
id: Int!
postedBy: User
url: String!
voters: [User!]!
}
That’s it! You can now access the createdAt
field in any Link
type. You can test this using the feed
query and adding createdAt
to the selection set like this:
query {
feed {
createdAt
description
id
url
}
}
Your result should look similar to the following:
{
"data": {
"feed": [
{
"createdAt": "2021-12-14T23:21:52.620Z",
"description": "Code-First GraphQL schemas for JavaScript/TypeScript",
"id": 1,
"url": "nexusjs.org"
},
{
"createdAt": "2021-12-15T04:20:33.616Z",
"description": "Next-generation Node.js and TypeScript ORM",
"id": 2,
"url": "www.prisma.io"
}
]
}
}
Note: If you want to learn more about custom GraphQL scalars in the context of the Node ecosystem, this article is worth reading. You can also check out the Nexus docs for understanding how to create and use custom scalars with Nexus.