Feature/Use fastify instead of express (#1)

- Replaced Apollo Server with Mercurius for GraphQL API
- Updated resolvers to use Mercurius-compatible GraphQL implementation
- Migrated from Express to Fastify for server framework
- Improved error handling with GraphQL error extensions
- Added Zod for environment variable validation
- Updated Prisma schema and migrations
- Configured CORS and WebSocket subscriptions
- Simplified GraphQL schema and resolver structure
- Enhanced type safety and code organization
- Replaced Apollo Server with Mercurius for GraphQL API
- Updated resolvers to use Mercurius-compatible GraphQL implementation
- Migrated from Express to Fastify for server framework
- Improved error handling with GraphQL error extensions
- Added Zod for environment variable validation
- Updated Prisma schema and migrations
- Configured CORS and WebSocket subscriptions
- Simplified GraphQL schema and resolver structure
- Enhanced type safety and code organization

Reviewed-on: #1
Co-authored-by: Jusemon <juansmm@outlook.com>
Co-committed-by: Jusemon <juansmm@outlook.com>
This commit is contained in:
Juan Sebastián Montoya 2025-03-06 19:15:56 -05:00 committed by Juan Sebastián Montoya
parent b4e5a04126
commit 6214b503bc
47 changed files with 4968 additions and 5424 deletions

View file

@ -0,0 +1,4 @@
import { config } from '@repo/eslint-config/solid-js';
/** @type {import("eslint").Linter.Config} */
export default config;

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Solid + TS</title>
<title>Ultimate Chat</title>
</head>
<body>
<div id="root"></div>

View file

@ -5,20 +5,21 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"check-types": "tsc --noEmit"
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@urql/core": "^5.1.1",
"graphql": "^16.8.1",
"@urql/solid": "^0.1.2",
"graphql-ws": "^6.0.4",
"solid-js": "^1.8.15",
"@urql/solid": "^0.1.2"
"solid-js": "^1.9.5",
"zod": "^3.24.2"
},
"devDependencies": {
"typescript": "^5.2.2",
"vite": "^5.1.4",
"vite-plugin-solid": "^2.10.1"
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"typescript": "5.8.2",
"vite": "^6.2.0",
"vite-plugin-solid": "^2.11.2"
}
}

View file

@ -28,7 +28,7 @@ function App() {
// Call checkAuth on component mount
checkAuth();
const handleLoginSuccess = (token: string, id: string) => {
const handleLoginSuccess = (_: string, id: string) => {
setIsAuthenticated(true);
setUserId(id);
};

View file

@ -79,17 +79,23 @@ export function RoomList(props: RoomListProps) {
});
// Subscribe to new rooms
const [roomAddedSubscription] = createSubscription({
const [roomAddedSubscription] = createSubscription<{
roomAdded: Room;
}>({
query: ROOM_ADDED_SUBSCRIPTION,
});
// Subscribe to room updates (when members change)
const [roomUpdatedSubscription] = createSubscription({
const [roomUpdatedSubscription] = createSubscription<{
roomUpdated: Room;
}>({
query: ROOM_UPDATED_SUBSCRIPTION,
});
// Join room mutation
const [joinRoomResult, joinRoom] = createMutation(JOIN_ROOM_MUTATION);
const [joinRoomResult, joinRoom] = createMutation<{
joinRoom: Room;
}>(JOIN_ROOM_MUTATION);
// Load initial rooms
createEffect(() => {
@ -150,7 +156,7 @@ export function RoomList(props: RoomListProps) {
setRooms((prev) =>
prev.map((room) =>
room.id === roomId
? { ...room, members: result.data.joinRoom.members }
? { ...room, members: result.data!.joinRoom.members }
: room
)
);

View file

@ -1,8 +1,8 @@
/* @refresh reload */
import { render } from 'solid-js/web'
import './index.css'
import App from './App.tsx'
import { render } from 'solid-js/web';
import './index.css';
import App from './App.tsx';
const root = document.getElementById('root')
const root = document.getElementById('root');
render(() => <App />, root!)
render(() => <App />, root!);

View file

@ -1,16 +1,20 @@
import { z } from 'zod';
import { createClient, fetchExchange, subscriptionExchange } from '@urql/core';
import { createClient as createWSClient } from 'graphql-ws';
import { createClient as createWsClient } from 'graphql-ws';
// Get API URLs from environment variables
const API_URL =
import.meta.env.VITE_API_URL || 'https://chat-api.jusemon.com/graphql';
const WS_URL =
import.meta.env.VITE_WS_URL || 'wss://chat-api.jusemon.com/graphql';
const envSchema = z
.object({ VITE_API_URL: z.string(), VITE_WS_URL: z.string() })
.transform((env) => ({
API_URL: env.VITE_API_URL,
WS_URL: env.VITE_WS_URL,
}));
const { API_URL, WS_URL } = envSchema.parse(import.meta.env);
console.log('Current API_URL', API_URL);
console.log('Current WS_URL', WS_URL);
// Create a WebSocket client for GraphQL subscriptions
const wsClient = createWSClient({
const wsClient = createWsClient({
url: WS_URL,
});
@ -23,11 +27,8 @@ export const client = createClient({
forwardSubscription: (operation) => ({
subscribe: (sink) => {
const dispose = wsClient.subscribe(
{
...operation,
query: operation.query || '',
},
sink as any
{ ...operation, query: operation.query || '' },
sink
);
return {
unsubscribe: dispose,

View file

@ -1 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_WS_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View file

@ -1,27 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

View file

@ -1,7 +1,28 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
"extends": "@repo/typescript-config/solid.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

View file

@ -1,24 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View file

@ -1,6 +1,7 @@
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
import { defineConfig } from 'vite';
import solid from 'vite-plugin-solid';
export default defineConfig({
envDir: '../../',
plugins: [solid()],
})
});