Compare commits

...

No commits in common. "mpabi" and "dev" have entirely different histories.
mpabi ... dev

25 changed files with 13625 additions and 179 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
# Root
node_modules/
# Backend
backend/node_modules/
backend/database.sqlite
backend/dist/
# Frontend
frontend/node_modules
frontend/build

5393
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
backend/package.json Normal file
View File

@ -0,0 +1,18 @@
{
"dependencies": {
"-": "^0.0.1",
"fastify": "^5.2.2",
"graphql": "^16.10.0",
"mercurius": "^16.1.0",
"metadata": "^0.1.0",
"reflect": "^0.1.3",
"reflect-metadata": "^0.2.2",
"sqlite3": "^5.1.7",
"typeorm": "^0.3.21",
"typeorm-fastify-plugin": "^3.0.0"
},
"devDependencies": {
"@types/node": "^22.13.14",
"@vitejs/plugin-react": "^4.3.4"
}
}

16
backend/src/db.ts Normal file
View File

@ -0,0 +1,16 @@
import 'reflect-metadata';
import dbConnection from 'typeorm-fastify-plugin';
import { User } from './entity/User';
import { Post } from './entity/Post';
import { FastifyInstance } from 'fastify';
export async function registerDb(fastify: FastifyInstance) {
fastify.register(dbConnection, {
type: 'sqlite',
database: 'database.sqlite',
entities: [User, Post],
synchronize: true,
logging: true,
})
}

View File

@ -0,0 +1,14 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './User';
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(() => User, (user) => user.posts)
author: User;
}

View File

@ -0,0 +1,14 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Post } from './Post';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
}

82
backend/src/index.ts Normal file
View File

@ -0,0 +1,82 @@
import 'reflect-metadata';
import Fastify from 'fastify';
import mercurius from 'mercurius';
import { registerDb } from './db';
import { User } from './entity/User';
import { Post } from './entity/Post';
const fastify = Fastify( {
logger: true
});
const schema = `
type User {
id: ID!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
}
type Mutation {
createUser(name: String!): User
createPost(title: String!, userId: ID!): Post
}
`
const resolvers = {
Query: {
user: async (_: unknown, { id }: {id: number} ) => {
const userRepository = fastify.orm.getRepository(User)
return await userRepository.findOne({where: { id}, relations: ['posts']})
}
},
Mutation: {
createUser: async (_: unknown, { name }: {name: string}) => {
const user = new User();
user.name = name;
const userRepository = fastify.orm.getRepository(User)
await userRepository.save(user);
return user;
},
createPost: async (_: unknown, { title, userId }: {title: string, userId: number}) => {
const userRepository = fastify.orm.getRepository(User)
const user = await userRepository.findOne({ where: { id: userId } });
if (!user) {
throw new Error("User not found");
}
const postRepository = fastify.orm.getRepository(Post)
const post = new Post();
post.title = title;
post.author = user;
await postRepository.save(post);
return post;
}
}
}
registerDb(fastify);
fastify.register(mercurius, {
schema,
resolvers,
graphiql: true
})
fastify.listen({ port: 3001, host: '127.0.0.1'}, function (err, address) {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log(`Server listening on ${address}`);
})

14
backend/tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strictPropertyInitialization": false
},
"include": ["src"]
}

Binary file not shown.

View File

@ -1,179 +0,0 @@
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{listings}
\usepackage{xcolor}
% Configure colors for code
\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
\definecolor{codepurple}{rgb}{0.58,0,0.82}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
% Code listing style
\lstdefinestyle{mystyle}{
backgroundcolor=\color{backcolour},
commentstyle=\color{codegreen},
keywordstyle=\color{magenta},
stringstyle=\color{codepurple},
basicstyle=\ttfamily\footnotesize,
breakatwhitespace=false,
breaklines=true,
captionpos=b,
keepspaces=true,
numbers=left,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=2
}
\lstset{style=mystyle}
\title{Instructions for Integrating Fastify, GraphQL, and TypeORM}
\author{}
\date{}
\begin{document}
\maketitle
\section*{Step 1: Install Dependencies}
Install the required packages using npm:
\begin{lstlisting}[language=bash]
npm install fastify mercurius typeorm reflect-metadata sqlite3 graphql
\end{lstlisting}
\section*{Step 2: Configure TypeORM}
Create a file named \texttt{ormconfig.json} with the following content:
\begin{lstlisting}[language=json]
{
"type": "sqlite",
"database": "./db.sqlite",
"synchronize": true,
"logging": false,
"entities": ["src/entity/**/*.ts"],
"migrations": ["src/migration/**/*.ts"],
"subscribers": ["src/subscriber/**/*.ts"]
}
\end{lstlisting}
Next, create entities in the \texttt{src/entity} folder. Example of \texttt{User.ts}:
\begin{lstlisting}[language=typescript]
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Post } from './Post';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
}
\end{lstlisting}
Example of \texttt{Post.ts}:
\begin{lstlisting}[language=typescript]
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './User';
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(() => User, (user) => user.posts)
author: User;
}
\end{lstlisting}
\section*{Step 3: Configure Fastify with GraphQL}
Create a file named \texttt{index.ts} with the following content:
\begin{lstlisting}[language=typescript]
import 'reflect-metadata';
import { createConnection } from 'typeorm';
import fastify from 'fastify';
import mercurius from 'mercurius';
import { User } from './entity/User';
import { Post } from './entity/Post';
const app = fastify();
const schema = `
type User {
id: ID!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
}
`;
const resolvers = {
Query: {
user: async (_, { id }) => {
return await User.findOne({ where: { id }, relations: ['posts'] });
},
},
};
const startServer = async () => {
await createConnection();
app.register(mercurius, {
schema,
resolvers,
graphiql: true,
});
app.listen({ port: 3000 }, (err, address) => {
if (err) {
console.error(err);
process.exit(1);
}
console.log(`🚀 Server ready at ${address}`);
});
};
startServer();
\end{lstlisting}
\section*{Step 4: Testing}
1. Start the server:
\begin{lstlisting}[language=bash]
npx ts-node index.ts
\end{lstlisting}
2. Open your browser at:
\begin{lstlisting}[language=text]
http://localhost:3000/graphiql
\end{lstlisting}
3. Send a sample GraphQL query:
\begin{lstlisting}[language=graphql]
query {
user(id: 1) {
name
posts {
title
}
}
}
\end{lstlisting}
\end{document}

7
frontend/.eslintrc Normal file
View File

@ -0,0 +1,7 @@
{
"extends": ["airbnb", "prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": ["error"]
}
}

4
frontend/.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"singleQuote": false,
"printWidth": 120,
}

32
frontend/eslint.config.js Normal file
View File

@ -0,0 +1,32 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [
js.configs.recommended,
...tseslint.configs.recommended,
],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react/prop-types': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
);

12
frontend/index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Test react page</title>
<link rel="icon" href="/favicon.ico"/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

5860
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
frontend/package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"type": "commonjs",
"main": "index.js",
"devDependencies": {
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.2",
"@typescript-eslint/eslint-plugin": "^8.29.1",
"@typescript-eslint/parser": "^8.29.1",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^4.6.2",
"typescript": "^5.8.3",
"vite": "^6.2.5"
},
"scripts": {
"start": "vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0",
"vite-tsconfig-paths": "^5.1.4"
}
}

9
frontend/src/App.tsx Normal file
View File

@ -0,0 +1,9 @@
function App() {
return (
<div style={{ padding: '2rem' }}>
<h1>Hello from React!</h1>
</div>
);
}
export default App

9
frontend/src/main.tsx Normal file
View File

@ -0,0 +1,9 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)

1
frontend/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

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

7
frontend/tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View File

@ -0,0 +1,24 @@
{
"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"]
}

17
frontend/vite.config.js Normal file
View File

@ -0,0 +1,17 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import viteTsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig(() => {
return {
build: {
outDir: 'build',
},
plugins: [react(), viteTsconfigPaths()],
server: {
proxy: {
'/graphql': 'http://localhost:3001'
}
}
};
});

2011
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

8
package.json Normal file
View File

@ -0,0 +1,8 @@
{
"private": true,
"scripts": {
"dev": "npm-run-all --parallel dev:*",
"dev:backend": "cd backend && npx ts-node src/index.ts",
"dev:frontend": "cd frontend && npm start"
}
}