Compare commits

...

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

25 changed files with 179 additions and 13625 deletions

11
.gitignore vendored
View File

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

5393
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,16 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -1,14 +0,0 @@
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[];
}

View File

@ -1,82 +0,0 @@
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}`);
})

View File

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

BIN
doc/main.pdf Normal file

Binary file not shown.

179
doc/main.tex Normal file
View File

@ -0,0 +1,179 @@
\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}

View File

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

View File

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

View File

@ -1,32 +0,0 @@
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 },
],
},
}
);

View File

@ -1,12 +0,0 @@
<!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>

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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,17 +0,0 @@
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

File diff suppressed because it is too large Load Diff

View File

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