david
david5mo ago

All backend imports becoming accessible on frontend?

Node Environment Version: v20.10.0 Package Manager: PNPM Workspaces Repo Setup: Monorepo/Turborepo Environment: WSL with VSCode TRPC Versions:
"@trpc/client": "11.0.0-rc.403",
"@trpc/react-query": "11.0.0-rc.403",
"@trpc/server": "11.0.0-rc.403",
"@trpc/client": "11.0.0-rc.403",
"@trpc/react-query": "11.0.0-rc.403",
"@trpc/server": "11.0.0-rc.403",
Hello! First time user of TRPC. I've setup my project, and now have it so that it exports appRouter on the backend Backend
export type AppRouter = typeof appRouter;
export type AppRouter = typeof appRouter;
Then on the frontend like the tutorial I have a hooks file for trpc that imports the backend Frontend
import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "../../../../../backend/src";

export const trpc = createTRPCReact<AppRouter>({});
import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "../../../../../backend/src";

export const trpc = createTRPCReact<AppRouter>({});
The structure is setup as follows
apps/
├─ backend/
├─ frontend/
apps/
├─ backend/
├─ frontend/
However in VSCode all other backend imports are displayed during autoimport now in the frontend project? For example I can now do something like
import User from "../../../../../backend/src/models/User";
import User from "../../../../../backend/src/models/User";
When the line to import AppRouter is removed, none of the backend autocomplete imports appear. I realistically would only like to expose that type and nothing else, I don't think imports actually work, as in Vite/React will throw errors when you try to use that backend import them during runtime. But is there a way to prevent the other imports from appearing? Is this behaviour expected? I also tried to use pnpm link and export only a file containing the AppRouter but this didn't seem to prevent it either Backend package.json
"exports": {
".": "./src/types/trpc.types.ts"
},
"exports": {
".": "./src/types/trpc.types.ts"
},
Backend trpc.types.ts
import { appRouter } from "../app";

export type AppRouter = typeof appRouter;
import { appRouter } from "../app";

export type AppRouter = typeof appRouter;
Frontend
import type { AppRouter } from "backend";

// This still shows when writing "import Us.." for example.
import User from "node_modules/backend/src/models/User";
import type { AppRouter } from "backend";

// This still shows when writing "import Us.." for example.
import User from "node_modules/backend/src/models/User";
6 Replies
david
davidOP5mo ago
If it's of use here is my Typescript configs for the backend/frontend Backend
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": false,
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": "./",
"skipLibCheck": true
},
"include": ["src/**/*.ts", "environment.d.ts"],
"exclude": ["node_modules"],
"ts-node": {
"swc": true
}
}
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": false,
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": "./",
"skipLibCheck": true
},
"include": ["src/**/*.ts", "environment.d.ts"],
"exclude": ["node_modules"],
"ts-node": {
"swc": true
}
}
Frontend
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": "."
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": "."
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
Thanks for any help 🙏 If there's anything you need let me know Just as an update, I found somewhat if a half solution. But definitely not fully resolved. I've then added the following rule to my
.eslintrc.cjs
.eslintrc.cjs
config on the frontend
rules: {
"no-restricted-imports": [
"error",
{
patterns: ["backend/src/**"],
},
],
},
rules: {
"no-restricted-imports": [
"error",
{
patterns: ["backend/src/**"],
},
],
},
And doing something like
- import User from 'backend/src/models/User'
- import User from 'backend/src/models/User'
Will error with
'backend/src/models/User' import is restricted from being used by a pattern.eslintno-restricted-imports
'backend/src/models/User' import is restricted from being used by a pattern.eslintno-restricted-imports
Any direction would be great :feelsadman:
Andrew
Andrew5mo ago
I've been pretty surprised by how blase the internet has been about making the backend server package a dependency of the frontend with tRPC. It seems quite risky to accidentally pull in sensitive backend code. Here's what we did: 1. Add as the backend package as a TS project reference, and use a special path so it's easy to know:
{
"compilerOptions": {
"paths": {
"special-secret-trpc-type-sharing-path": ["../backend/src/trpc/router"],
}
},
"references": [{ "path": "../backend" }]
}
{
"compilerOptions": {
"paths": {
"special-secret-trpc-type-sharing-path": ["../backend/src/trpc/router"],
}
},
"references": [{ "path": "../backend" }]
}
2. Then in your frontend code:
import type { AppRouter } from "special-secret-trpc-type-sharing-path";

// We need to create an explicit type, so it can be shared as an annotation in other modules.
// Without it, type checking and code navigation breaks.
export type TRPCClient = ReturnType<typeof createTRPCClient<AppRouter>>;

export const trpcClient = createTRPCClient<AppRouter>({ ... })
import type { AppRouter } from "special-secret-trpc-type-sharing-path";

// We need to create an explicit type, so it can be shared as an annotation in other modules.
// Without it, type checking and code navigation breaks.
export type TRPCClient = ReturnType<typeof createTRPCClient<AppRouter>>;

export const trpcClient = createTRPCClient<AppRouter>({ ... })
3. Don't add the backend package to your frontend package.json. This means when you write out the type manually, TS knows what to do with it, but intellisense won't try to serve up exports from backend when you're in the frontend code. It feels incredibly jank and hacky, but so far it seems to be working.
david
davidOP5mo ago
Thanks @Andrew , appreciate your help with this. And I do agree, felt quite nervous about importing the backend just struggling to find solutions hah. I've given it a shot but might have either misinterpreted what you've said or am doing something wrong as Intellisense still seems to serve other exports. I've created a minimal repo and have also reset my VSCode settings/removed all extensions. Using the TS configs from my main application and then slightly modified trpc's minimal app. https://github.com/trpc/examples-minimal/tree/main https://github.com/davidtjones02/trpc-import-exclusion Any thoughts? Thanks again let me know if you need anything additionally.
GitHub
GitHub - trpc/examples-minimal
Contribute to trpc/examples-minimal development by creating an account on GitHub.
GitHub
GitHub - davidtjones02/trpc-import-exclusion
Contribute to davidtjones02/trpc-import-exclusion development by creating an account on GitHub.
No description
david
davidOP5mo ago
I think I can see benefit regardless versus importing the backend as a package like you've said however.
Andrew
Andrew5mo ago
Oh I haven't tried in VS Code tbh, I work with WebStorm so the intellisense logic may be different (though usually it's the same)... Ooooof yep just confirmed that VS Code just imports from the backend package. Whyyyy 😭
david
davidOP5mo ago
Argh so frustrating! Thank you for all the help though. The differences are still meaningful, and the ESLint rules are an added measure Magic library tRPC. And possibly that Webstorm just has superior contextual understanding of a repo versus VSCode But if anyone has any additional thoughts would be interesting to hear. For now though, I think the ESLint rules and not importing via a package will do for now 🙌