- {hello.data ?
{hello.data.greeting}
:
Loading..
}
+
+
+
+ Create T3 App
+
+
+
+
First Steps →
+
+ Just the basics - Everything you need to know to set up your database and
+ authentication.
+
+
+
+
Documentation →
+
+ Learn more about Create T3 App, the libraries it uses, and how to deploy it.
+
+
+
+
+
+ {hello.data ? hello.data.greeting : 'Loading tRPC query...'}
+
+
+
>
diff --git a/src/server/common/get-server-auth-session.ts b/src/server/common/get-server-auth-session.ts
index bd2a59c..35649f1 100644
--- a/src/server/common/get-server-auth-session.ts
+++ b/src/server/common/get-server-auth-session.ts
@@ -1,13 +1,13 @@
-// Wrapper for unstable_getServerSession https://next-auth.js.org/configuration/nextjs
-
-import { authOptions as nextAuthOptions } from '../../pages/api/auth/[...nextauth]';
+import { authOptions } from '../../pages/api/auth/[...nextauth]';
+import { type GetServerSidePropsContext } from 'next';
import { unstable_getServerSession } from 'next-auth';
-import type { GetServerSidePropsContext } from 'next';
-
-// Next API route example - /pages/api/restricted.ts
+/**
+ * Wrapper for unstable_getServerSession https://next-auth.js.org/configuration/nextjs
+ * See example usage in trpc createContext or the restricted API route
+ */
export const getServerAuthSession = async (ctx: {
req: GetServerSidePropsContext['req'];
res: GetServerSidePropsContext['res'];
-}) => unstable_getServerSession(ctx.req, ctx.res, nextAuthOptions);
+}) => unstable_getServerSession(ctx.req, ctx.res, authOptions);
diff --git a/src/server/db/client.ts b/src/server/db/client.ts
index e9a42a3..5d7442a 100644
--- a/src/server/db/client.ts
+++ b/src/server/db/client.ts
@@ -1,12 +1,10 @@
/* eslint-disable import/extensions */
-/* eslint-disable vars-on-top */
-// src/server/db/client.ts
import { env } from '../../env/server.mjs';
import { PrismaClient } from '@prisma/client';
declare global {
- // eslint-disable-next-line no-var
+ // eslint-disable-next-line no-var, vars-on-top
var prisma: PrismaClient | undefined;
}
diff --git a/src/server/router/context.ts b/src/server/router/context.ts
deleted file mode 100644
index 6efb365..0000000
--- a/src/server/router/context.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-// src/server/router/context.ts
-import { getServerAuthSession } from '../common/get-server-auth-session';
-import { prisma } from '../db/client';
-
-import * as trpc from '@trpc/server';
-import * as trpcNext from '@trpc/server/adapters/next';
-import { Session } from 'next-auth';
-
-interface CreateContextOptions {
- session: Session | null;
-}
-
-/** Use this helper for:
- * - testing, where we dont have to Mock Next.js' req/res
- * - trpc's `createSSGHelpers` where we don't have req/res
- * */
-export const createContextInner = async (opts: CreateContextOptions) => ({
- session: opts.session,
- prisma,
-});
-
-/**
- * This is the actual context you'll use in your router
- * @link https://trpc.io/docs/context
- * */
-export const createContext = async (opts: trpcNext.CreateNextContextOptions) => {
- const { req, res } = opts;
-
- // Get the session from the server using the unstable_getServerSession wrapper function
- const session = await getServerAuthSession({ req, res });
-
- return createContextInner({
- session,
- });
-};
-
-type Context = trpc.inferAsyncReturnType
;
-
-export const createRouter = () => trpc.router();
-
-/**
- * Creates a tRPC router that asserts all queries and mutations are from an authorized user. Will throw an unauthorized error if a user is not signed in.
- * */
-export function createProtectedRouter() {
- return createRouter().middleware(({ ctx, next }) => {
- if (!ctx.session || !ctx.session.user) {
- throw new trpc.TRPCError({ code: 'UNAUTHORIZED' });
- }
- return next({
- ctx: {
- ...ctx,
- // infers that `session` is non-nullable to downstream resolvers
- session: { ...ctx.session, user: ctx.session.user },
- },
- });
- });
-}
diff --git a/src/server/router/example.ts b/src/server/router/example.ts
deleted file mode 100644
index 205b479..0000000
--- a/src/server/router/example.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { createRouter } from './context';
-
-import { z } from 'zod';
-
-export const exampleRouter = createRouter()
- .query('hello', {
- input: z
- .object({
- text: z.string().nullish(),
- })
- .nullish(),
- resolve({ input }) {
- return {
- greeting: `Hello ${input?.text ?? 'world'}`,
- };
- },
- })
- .query('getAll', {
- async resolve({ ctx }) {
- return ctx.prisma.example.findMany();
- },
- });
diff --git a/src/server/router/index.ts b/src/server/router/index.ts
deleted file mode 100644
index e009472..0000000
--- a/src/server/router/index.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-// src/server/router/index.ts
-import { createRouter } from './context';
-import { exampleRouter } from './example';
-import { protectedExampleRouter } from './protected-example-router';
-
-import superjson from 'superjson';
-
-export const appRouter = createRouter()
- .transformer(superjson)
- .merge('example.', exampleRouter)
- .merge('auth.', protectedExampleRouter);
-
-// export type definition of API
-export type AppRouter = typeof appRouter;
diff --git a/src/server/router/protected-example-router.ts b/src/server/router/protected-example-router.ts
deleted file mode 100644
index 08763e2..0000000
--- a/src/server/router/protected-example-router.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { createProtectedRouter } from './context';
-
-// Example router with queries that can only be hit if the user requesting is signed in
-export const protectedExampleRouter = createProtectedRouter()
- .query('getSession', {
- resolve({ ctx }) {
- return ctx.session;
- },
- })
- .query('getSecretMessage', {
- resolve() {
- return 'He who asks a question is a fool for five minutes; he who does not ask a question remains a fool forever.';
- },
- });
diff --git a/src/server/trpc/context.ts b/src/server/trpc/context.ts
new file mode 100644
index 0000000..ed4c583
--- /dev/null
+++ b/src/server/trpc/context.ts
@@ -0,0 +1,37 @@
+import { getServerAuthSession } from '../common/get-server-auth-session';
+import { prisma } from '../db/client';
+
+import { type inferAsyncReturnType } from '@trpc/server';
+import { type CreateNextContextOptions } from '@trpc/server/adapters/next';
+import { type Session } from 'next-auth';
+
+interface CreateContextOptions {
+ session: Session | null;
+}
+
+/** Use this helper for:
+ * - testing, so we dont have to mock Next.js' req/res
+ * - trpc's `createSSGHelpers` where we don't have req/res
+ * @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts
+ * */
+export const createContextInner = async (opts: CreateContextOptions) => ({
+ session: opts.session,
+ prisma,
+});
+
+/**
+ * This is the actual context you'll use in your router
+ * @link https://trpc.io/docs/context
+ * */
+export const createContext = async (opts: CreateNextContextOptions) => {
+ const { req, res } = opts;
+
+ // Get the session from the server using the unstable_getServerSession wrapper function
+ const session = await getServerAuthSession({ req, res });
+
+ return createContextInner({
+ session,
+ });
+};
+
+export type Context = inferAsyncReturnType;
diff --git a/src/server/trpc/router/_app.ts b/src/server/trpc/router/_app.ts
new file mode 100644
index 0000000..439cf1a
--- /dev/null
+++ b/src/server/trpc/router/_app.ts
@@ -0,0 +1,12 @@
+import { authRouter } from './auth';
+import { exampleRouter } from './example';
+
+import { router } from '../trpc';
+
+export const appRouter = router({
+ example: exampleRouter,
+ auth: authRouter,
+});
+
+// export type definition of API
+export type AppRouter = typeof appRouter;
diff --git a/src/server/trpc/router/auth.ts b/src/server/trpc/router/auth.ts
new file mode 100644
index 0000000..876d56d
--- /dev/null
+++ b/src/server/trpc/router/auth.ts
@@ -0,0 +1,6 @@
+import { router, publicProcedure, protectedProcedure } from '../trpc';
+
+export const authRouter = router({
+ getSession: publicProcedure.query(({ ctx }) => ctx.session),
+ getSecretMessage: protectedProcedure.query(() => 'you can now see this secret message!'),
+});
diff --git a/src/server/trpc/router/example.ts b/src/server/trpc/router/example.ts
new file mode 100644
index 0000000..268a342
--- /dev/null
+++ b/src/server/trpc/router/example.ts
@@ -0,0 +1,12 @@
+import { router, publicProcedure } from '../trpc';
+
+import { z } from 'zod';
+
+export const exampleRouter = router({
+ hello: publicProcedure
+ .input(z.object({ text: z.string().nullish() }).nullish())
+ .query(({ input }) => ({
+ greeting: `Hello ${input?.text ?? 'world'}`,
+ })),
+ getAll: publicProcedure.query(({ ctx }) => ctx.prisma.example.findMany()),
+});
diff --git a/src/server/trpc/trpc.ts b/src/server/trpc/trpc.ts
new file mode 100644
index 0000000..66917bc
--- /dev/null
+++ b/src/server/trpc/trpc.ts
@@ -0,0 +1,39 @@
+import { type Context } from './context';
+
+import { initTRPC, TRPCError } from '@trpc/server';
+import superjson from 'superjson';
+
+const t = initTRPC.context().create({
+ transformer: superjson,
+ errorFormatter({ shape }) {
+ return shape;
+ },
+});
+
+export const { router } = t;
+
+/**
+ * Unprotected procedure
+ * */
+export const publicProcedure = t.procedure;
+
+/**
+ * Reusable middleware to ensure
+ * users are logged in
+ */
+const isAuthed = t.middleware(({ ctx, next }) => {
+ if (!ctx.session || !ctx.session.user) {
+ throw new TRPCError({ code: 'UNAUTHORIZED' });
+ }
+ return next({
+ ctx: {
+ // infers the `session` as non-nullable
+ session: { ...ctx.session, user: ctx.session.user },
+ },
+ });
+});
+
+/**
+ * Protected procedure
+ * */
+export const protectedProcedure = t.procedure.use(isAuthed);
diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts
index e25ea19..87bc304 100644
--- a/src/types/next-auth.d.ts
+++ b/src/types/next-auth.d.ts
@@ -1,12 +1,12 @@
-import { DefaultSession } from "next-auth";
+import { type DefaultSession } from 'next-auth';
-declare module "next-auth" {
+declare module 'next-auth' {
/**
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user?: {
id: string;
- } & DefaultSession["user"];
+ } & DefaultSession['user'];
}
}
diff --git a/src/utils/trpc.ts b/src/utils/trpc.ts
index 6f93378..0c4ef9f 100644
--- a/src/utils/trpc.ts
+++ b/src/utils/trpc.ts
@@ -1,24 +1,42 @@
-/* eslint-disable @typescript-eslint/naming-convention */
-// src/utils/trpc.ts
-import { createReactQueryHooks } from '@trpc/react';
+import { type AppRouter } from '../server/trpc/router/_app';
-import type { AppRouter } from '../server/router';
-import type { inferProcedureOutput, inferProcedureInput } from '@trpc/server';
+import { httpBatchLink, loggerLink } from '@trpc/client';
+import { createTRPCNext } from '@trpc/next';
+import { type inferRouterInputs, type inferRouterOutputs } from '@trpc/server';
+import superjson from 'superjson';
-export const trpc = createReactQueryHooks();
+const getBaseUrl = () => {
+ if (typeof window !== 'undefined') return ''; // browser should use relative url
+ if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
+ return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
+};
-/**
- * These are helper types to infer the input and output of query resolvers
- * @example type HelloOutput = inferQueryOutput<'hello'>
- */
-export type inferQueryOutput =
- inferProcedureOutput;
-
-export type inferQueryInput =
- inferProcedureInput;
-
-export type inferMutationOutput =
- inferProcedureOutput;
+export const trpc = createTRPCNext({
+ config() {
+ return {
+ transformer: superjson,
+ links: [
+ loggerLink({
+ enabled: (opts) =>
+ process.env.NODE_ENV === 'development' ||
+ (opts.direction === 'down' && opts.result instanceof Error),
+ }),
+ httpBatchLink({
+ url: `${getBaseUrl()}/api/trpc`,
+ }),
+ ],
+ };
+ },
+ ssr: false,
+});
-export type inferMutationInput =
- inferProcedureInput;
+/**
+ * Inference helper for inputs
+ * @example type HelloInput = RouterInputs['example']['hello']
+ * */
+export type RouterInputs = inferRouterInputs;
+/**
+ * Inference helper for outputs
+ * @example type HelloOutput = RouterOutputs['example']['hello']
+ * */
+export type RouterOutputs = inferRouterOutputs;
diff --git a/tsconfig.json b/tsconfig.json
index b0cc3a4..658068a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "target": "es5",
+ "target": "es2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
@@ -14,8 +14,7 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
- "noUncheckedIndexedAccess": true,
- "baseUrl": "src"
+ "noUncheckedIndexedAccess": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.mjs"],
"exclude": ["node_modules"]