Generated Types
To add to the TypeScript (and JavaScript!) experience, Redwood generates types for you. These generated types not only include your GraphQL operations, but also your named routes, Cells, scenarios, and tests.
When you run yarn rw dev
, the CLI watches files for changes and triggers type generation automatically, but you can trigger it manually too:
yarn rw g types
If you're getting errors trying to generate types, it's worth checking the GraphQL operations in your Cells and SDLs.
Make sure that they're syntactically valid, and that every query and mutation on the web side is defined in an *.sdl.js
file on the api side.
If you're curious, you can find the generated types in the .redwood/types
, web/types/graphql.d.ts
, and api/types/graphql.d.ts
directories. Broadly speaking, Redwood generates the following types:
- "mirror" types for your components, pages, layouts, etc. on the web side, and for your services, lib, etc. on the api side
- types based on your queries and mutations on the web side (in
web/types/graphql.d.ts
) - types for resolvers based on your SDLs on the api side (in
api/types/graphql.d.ts
) - types for testing,
currentUser
, etc. - types for certain functions like
routes.pageName()
anduseAuth()
CurrentUser
If you've setup auth, the type for the current user on both the web and the api side gets automatically "inferred" from the getCurrentUser
function in api/src/lib/auth.ts
.
For example, if you specify the return type on getCurrentUser
as...
interface MyCurrentUser {
id: string,
roles: string[],
email: string,
projectId: number
}
const getCurrentUser = ({decoded}): MyCurrentUser => {
//..
}
The types for both useAuth().currentUser
on the web side and context.currentUser
on the api side will be the same—the MyCurrentUser
interface.
Query and Mutation types
Let's say you have a query in a Cell that looks like this:
export const QUERY = gql`
# 👇 Make sure to name your GraphQL operations
query FindBlogPostQuery($id: Int!) {
blogPost: post(id: $id) {
title
body
}
}
`
Redwood generates types for both the data returned from the query and the query's variables.
These generated types will use the query's name—in this case, FindBlogPostQuery
—so you can import them like this:
import type { FindBlogPostQuery, FindBlogPostQueryVariables } from 'types/graphql'
FindBlogPostQuery
is the type of the data returned from the query ({ title: string, body: string }
) and FindBlogPostQueryVariables
is the type of the query's variables ({ id: number }
).
The import statement's specifier, 'types/graphql'
, is a mapped path. First, TypeScript will look for the types in web/types/graphql.d.ts
; if they're not there, it'll check types/graphql.d.ts
. Redwood only automatically generates the former. For the latter, see sharing types between sides.
But don't worry too much. If you use the generators, they template all of this for you!
Resolver Types
Generated Services include types for query and mutation resolvers:
import type { QueryResolvers, MutationResolvers } from 'types/graphql'
import { db } from 'src/lib/db'
export const posts: QueryResolvers['posts'] = () => {
return db.post.findMany()
}
export const post: QueryResolvers['post'] = ({ id }) => {
return db.post.findUnique({
where: { id },
})
}
These types help you by making sure you're returning an object in the shape of what you've defined in your SDL. If your Prisma model name matches the SDL type name, it'll be "mapped" i.e. the resolvers will expect you to return the Prisma type.
Note that these types expect you to return the complete type that you've defined in your Prisma schema. But you can just return the result of the Prisma query, and not have to worry about how, for example, a DateTime in Prisma maps to a String in GraphQL.
If the type doesn't match your Prisma models (by name), the TypeScript type will be generated based only on your definition in the SDL. So if you wish to return other properties that don't exist in your Prisma model type i.e. augment the prisma type with additional fields, you can change the type to a custom one in your SDL.
The resolver types help you by making sure you're returning an object in the shape of what you've defined in your SDL.
Lets say that in one of your SDLs, you define a union type
type OutOfStock {
message: String!
}
union CandyResult = Candy | OutOfStock
type Query {
candy(id: String!): CandyResult @skipAuth
}
These types will also be handled automatically. But if you're returning a different Prisma model (instead of something like the generic OutOfStock
type we have here, which is just a message), you may need to write your own resolver type, as the type generator won't know how to map the Prisma type to the GraphQL return type.
Under the Hood
Redwood uses GraphQL Code Generator (aka graphql-codegen) to generate types for your GraphQL operations and SDLs. It's even configured to use the types from your generated Prisma Client, to make sure that your resolvers are strongly typed!
Customizing GraphQL Code Generation
While the default settings are configured so that things just work️, you can customize them to your liking by adding a ./codegen.yml
file to the root of your project.
You can find them here in Redwood's source. Look for the generateTypeDefGraphQLWeb
and generateTypeDefGraphQLApi
functions.
For example, adding this codegen.yml
to the root of your project will transform the names of the generated types to UPPERCASE:
config:
namingConvention:
typeNames: change-case-all#upperCase
You can configure graphql-codegen in a number of different ways: codegen.yml
, codegen.json
, or codegen.js
. Even a codegen
key in your root package.json
will do. graphql-codegen uses cosmiconfig under the hood—take a look at their docs if you want to know more.
For completeness, here's the docs on configuring GraphQL Code Generator. Currently, Redwood only supports the root level config
option.
As a part of type generation, the VSCode GraphQL extension configures itself based on the merged schema Redwood generates in .redwood/schema.graphql
.
You can configure it further in graphql.config.js
at the root of your project.