Skip to content

Commit

Permalink
Merge pull request #57 from ixahmedxi/trpc
Browse files Browse the repository at this point in the history
feat: adds a trpc api
  • Loading branch information
ixahmedxi authored May 12, 2024
2 parents 359ecb6 + 4f0f72d commit 56f5bbc
Show file tree
Hide file tree
Showing 30 changed files with 468 additions and 64 deletions.
28 changes: 28 additions & 0 deletions .changeset/shaggy-experts-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
"@orbitkit/utils": patch
"@orbitkit/auth": patch
"@orbitkit/api": patch
"@orbitkit/ui": patch
"@orbitkit/docs": patch
"@orbitkit/web": patch
"@orbitkit/marketing": patch
"@orbitkit/assets": patch
"eslint-config-orbitkit": patch
"@orbitkit/storybook": patch
"@orbitkit/tailwind": patch
"@orbitkit/tsconfig": patch
"@orbitkit/vite": patch
"@orbitkit/core": patch
"@orbitkit/db": patch
"@orbitkit/env": patch
---

feat: creates a trpc api package

changes in this release:

- creates a new `packages/api` package that hosts a tRPC api to be used for the web application.
- renames the lucia auth `getSession` function to `auth` and provides a new uncached version of it.
- refactors the code in some places to be generally better.
- splits the utils package from a barrel export to multi-file export.
- `getBaseUrl` util now returns `window.location.origin` instead of an empty string when the `window` object is not `undefined`.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [**TypeScript**](https://www.typescriptlang.org/): type-safety is a core principle of OrbitKit.
- [**Astrojs**](https://astro.build): for a clean slate to build your marketing website on top.
- [**Next.js**](https://nextjs.org): Web application is included, giving you a solid foundation for your product.
- [**tRPC**](https://trpc.io) for a fully type-safe api.
- [**Mintlify**](https://mintlify.com): for a clean, fast, and easy to use platform to document your project.
- [**Turborepo**](https://turbo.build/repo): caching builds so you never have to run the same command twice.
- [**Drizzle ORM**](https://orm.drizzle.team): providing a fully type-safe way to interact with your database.
Expand Down
1 change: 1 addition & 0 deletions apps/docs/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ description: '🚀 OrbitKit is an enterprise ready monorepo StarterKit ready to
- [**TypeScript**](https://www.typescriptlang.org/): type-safety is a core principle of OrbitKit.
- [**Astrojs**](https://astro.build): for a clean slate to build your marketing website on top.
- [**Next.js**](https://nextjs.org): Web application is included, giving you a solid foundation for your product.
- [**tRPC**](https://trpc.io) for a fully type-safe api.
- [**Mintlify**](https://mintlify.com): for a clean, fast, and easy to use platform to document your project.
- [**Turborepo**](https://turbo.build/repo): caching builds so you never have to run the same command twice.
- [**Drizzle ORM**](https://orm.drizzle.team): providing a fully type-safe way to interact with your database.
Expand Down
7 changes: 6 additions & 1 deletion apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ const nextConfig = {
experimental: {
typedRoutes: true,
},
transpilePackages: ['@orbitkit/db', '@orbitkit/auth', '@orbitkit/env'],
transpilePackages: [
'@orbitkit/db',
'@orbitkit/auth',
'@orbitkit/env',
'@orbitkit/api',
],
};

export default nextConfig;
7 changes: 7 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@
"typecheck": "tsc --noEmit --tsBuildInfoFile .tsbuildinfo"
},
"dependencies": {
"@orbitkit/api": "workspace:^",
"@orbitkit/auth": "workspace:^",
"@orbitkit/db": "workspace:^",
"@orbitkit/env": "workspace:^",
"@orbitkit/ui": "workspace:^",
"@orbitkit/utils": "workspace:^",
"@t3-oss/env-nextjs": "^0.10.1",
"@tanstack/react-query": "^5.35.5",
"@total-typescript/ts-reset": "^0.5.1",
"@trpc/client": "next",
"@trpc/react-query": "next",
"@trpc/server": "next",
"@unkey/ratelimit": "^0.1.6",
"@uploadthing/react": "^6.5.3",
"geist": "^1.3.0",
Expand All @@ -29,6 +35,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"server-only": "^0.0.1",
"superjson": "^2.2.1",
"uploadthing": "^6.10.3",
"zod": "^3.23.8"
},
Expand Down
8 changes: 7 additions & 1 deletion apps/web/src/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Link from 'next/link';
import { redirect } from 'next/navigation';

import { auth } from '@orbitkit/auth';
import { env } from '@orbitkit/env/web';

const googleAuthIsEnabled =
Expand All @@ -10,7 +12,11 @@ const googleAuthIsEnabled =
const githubAuthIsEnabled =
env.AUTH_GITHUB_SECRET !== undefined && env.AUTH_GITHUB_ID !== undefined;

export default function Page() {
export default async function Page() {
const { user } = await auth();

if (user) redirect('/');

return (
<main className="container mx-auto flex flex-col">
{githubAuthIsEnabled && (
Expand Down
28 changes: 28 additions & 0 deletions apps/web/src/app/api/trpc/[trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { type NextRequest } from 'next/server';

import { fetchRequestHandler } from '@trpc/server/adapters/fetch';

import { appRouter, createTRPCContext } from '@orbitkit/api';
import { env } from '@orbitkit/env/web';

const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};

const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => createContext(req),
onError: ({ path, error }) => {
env.NODE_ENV === 'development' &&
console.error(
`❌ tRPC failed on ${path ?? '<no-path>'}: ${error.message}`,
);
},
});

export { handler as GET, handler as POST };
4 changes: 2 additions & 2 deletions apps/web/src/app/api/uploadthing/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import type { FileRouter } from 'uploadthing/next';
import { createUploadthing } from 'uploadthing/next';
import { UploadThingError } from 'uploadthing/server';

import { getSession } from '@orbitkit/auth';
import { auth } from '@orbitkit/auth';

const f = createUploadthing();

export const fileRouter = {
imageUploader: f({ image: { maxFileSize: '4MB' } })
.middleware(async () => {
const session = await getSession();
const session = await auth();

if (!session.user)
// eslint-disable-next-line @typescript-eslint/only-throw-error
Expand Down
6 changes: 4 additions & 2 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { extractRouterConfig } from 'uploadthing/server';

import { Toaster } from '@orbitkit/ui/toast';

import { TRPCReactProvider } from '@/lib/trpc/react';

import { fileRouter } from './api/uploadthing/core';

export const metadata: Metadata = {
Expand All @@ -24,11 +26,11 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
<body className={`${GeistSans.variable} ${GeistMono.variable}`}>
<NextSSRPlugin routerConfig={extractRouterConfig(fileRouter)} />
<ThemeProvider attribute="class" enableSystem>
{children}
<TRPCReactProvider>{children}</TRPCReactProvider>
<Toaster />
</ThemeProvider>
</body>
Expand Down
12 changes: 7 additions & 5 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { redirect } from 'next/navigation';

import { getSession } from '@orbitkit/auth';
import { auth } from '@orbitkit/auth';
import { logout } from '@orbitkit/auth/actions/logout';
import { Avatar, AvatarFallback, AvatarImage } from '@orbitkit/ui/avatar';

import { ThemeSwitcher } from '@/components/ThemeSwitcher';
import { api } from '@/lib/trpc/server';

import { ShowToast } from './show-toast';
import { UploadExample } from './upload';

export default async function Home() {
const { user } = await getSession();
if (!user) {
return redirect('/login');
}
const { user } = await auth();
if (!user) redirect('/login');

const hello = await api.hello.protected();

return (
<main className="container mx-auto py-6 px-6">
Expand All @@ -35,6 +36,7 @@ export default async function Home() {
</form>
<ShowToast />
<UploadExample />
<p>{hello.message}</p>
</main>
);
}
60 changes: 60 additions & 0 deletions apps/web/src/lib/trpc/react.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client';

import { useState } from 'react';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { loggerLink, unstable_httpBatchStreamLink } from '@trpc/client';
import { createTRPCReact } from '@trpc/react-query';
import { type inferRouterInputs, type inferRouterOutputs } from '@trpc/server';
import superjson from 'superjson';

import { type AppRouter } from '@orbitkit/api';
import { getBaseUrl } from '@orbitkit/utils/url';

const createQueryClient = () => new QueryClient();

let clientQueryClientSingleton: QueryClient | undefined = undefined;
const getQueryClient = () => {
if (typeof window === 'undefined') {
return createQueryClient();
}
return (clientQueryClientSingleton ??= createQueryClient());
};

export const api = createTRPCReact<AppRouter>();

export type RouterInputs = inferRouterInputs<AppRouter>;
export type RouterOutputs = inferRouterOutputs<AppRouter>;

export function TRPCReactProvider(props: { children: React.ReactNode }) {
const queryClient = getQueryClient();

const [trpcClient] = useState(() =>
api.createClient({
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
transformer: superjson,
url: getBaseUrl() + '/api/trpc',
headers: () => {
const headers = new Headers();
headers.set('x-trpc-source', 'nextjs-react');
return headers;
},
}),
],
}),
);

return (
<QueryClientProvider client={queryClient}>
<api.Provider client={trpcClient} queryClient={queryClient}>
{props.children}
</api.Provider>
</QueryClientProvider>
);
}
17 changes: 17 additions & 0 deletions apps/web/src/lib/trpc/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'server-only';

import { cache } from 'react';
import { headers } from 'next/headers';

import { createCaller, createTRPCContext } from '@orbitkit/api';

const createContext = cache(async () => {
const heads = new Headers(headers());
heads.set('x-trpc-source', 'rsc');

return createTRPCContext({
headers: heads,
});
});

export const api = createCaller(createContext);
3 changes: 3 additions & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,14 @@ words:
- starterkit
- starterkits
- stylesheet
- superjson
- tada
- tailwindcss
- tanstack
- thollander
- todos
- topbar
- trpc
- tsbuildinfo
- tsconfigs
- tsup
Expand Down
7 changes: 7 additions & 0 deletions packages/api/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('eslint').Linter.Config} */
const config = {
root: true,
extends: ['orbitkit/base'],
};

module.exports = config;
34 changes: 34 additions & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@orbitkit/api",
"version": "0.1.3",
"private": true,
"description": "A tRPC API package used in the web application",
"license": "MIT",
"author": "OrbitKit",
"sideEffects": false,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"lint": "eslint . --cache --max-warnings 0",
"typecheck": "tsc --noEmit --tsBuildInfoFile .tsbuildinfo"
},
"dependencies": {
"@orbitkit/auth": "workspace:^",
"@orbitkit/db": "workspace:^",
"@orbitkit/env": "workspace:^",
"@orbitkit/utils": "workspace:^",
"@trpc/server": "next",
"superjson": "^2.2.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@orbitkit/tsconfig": "workspace:^",
"@types/node": "^20.12.11",
"eslint-config-orbitkit": "workspace:^"
},
"volta": {
"extends": "../../package.json"
}
}
12 changes: 12 additions & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { helloRouter } from './routers/hello';
import { createCallerFactory, createRouter } from './trpc';

export const appRouter = createRouter({
hello: helloRouter,
});

export type AppRouter = typeof appRouter;

export const createCaller = createCallerFactory(appRouter);

export { createTRPCContext } from './trpc';
9 changes: 9 additions & 0 deletions packages/api/src/routers/hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createRouter, protectedProcedure } from '../trpc';

export const helloRouter = createRouter({
protected: protectedProcedure.query(({ ctx }) => {
return {
message: `Hello, ${ctx.user.name ?? 'world'} from tRPC!`,
};
}),
});
Loading

0 comments on commit 56f5bbc

Please sign in to comment.