@anmol0493/fullstack-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/backend-express/.env.example +2 -0
  4. package/backend-express/index.js +2 -0
  5. package/backend-express/package-lock.json +1939 -0
  6. package/backend-express/package.json +25 -0
  7. package/backend-express/src/config/db.js +20 -0
  8. package/backend-express/src/controller/auth.js +71 -0
  9. package/backend-express/src/middleware/auth.js +36 -0
  10. package/backend-express/src/middleware/validator.js +16 -0
  11. package/backend-express/src/models/user.js +26 -0
  12. package/backend-express/src/routes/auth.js +25 -0
  13. package/backend-express/src/server.js +28 -0
  14. package/backend-express/src/utils/constants.js +14 -0
  15. package/backend-express/src/utils/helper.js +30 -0
  16. package/backend-express/src/utils/schema/auth.js +34 -0
  17. package/backend-nestjs/.env.example +3 -0
  18. package/backend-nestjs/.prettierrc +4 -0
  19. package/backend-nestjs/README.md +99 -0
  20. package/backend-nestjs/eslint.config.mjs +35 -0
  21. package/backend-nestjs/nest-cli.json +8 -0
  22. package/backend-nestjs/package.json +99 -0
  23. package/backend-nestjs/pnpm-lock.yaml +7848 -0
  24. package/backend-nestjs/src/app.controller.ts +12 -0
  25. package/backend-nestjs/src/app.module.ts +13 -0
  26. package/backend-nestjs/src/app.service.ts +8 -0
  27. package/backend-nestjs/src/common/decorators/user.decorator.ts +8 -0
  28. package/backend-nestjs/src/common/dtos/common.dto.ts +87 -0
  29. package/backend-nestjs/src/common/enum/index.ts +0 -0
  30. package/backend-nestjs/src/common/exceptions/custom.exception.ts +28 -0
  31. package/backend-nestjs/src/common/filters/http-exception.filter.ts +46 -0
  32. package/backend-nestjs/src/common/guard/permission.guard.ts +18 -0
  33. package/backend-nestjs/src/common/middleware/auth.middleware.ts +20 -0
  34. package/backend-nestjs/src/common/pipes/validation.pipe.ts +61 -0
  35. package/backend-nestjs/src/common/utils/constants.ts +36 -0
  36. package/backend-nestjs/src/common/utils/helper.ts +43 -0
  37. package/backend-nestjs/src/core/core.module.ts +8 -0
  38. package/backend-nestjs/src/core/jwt/jwt.module.ts +10 -0
  39. package/backend-nestjs/src/core/jwt/jwt.service.ts +45 -0
  40. package/backend-nestjs/src/core/prisma/prisma.module.ts +9 -0
  41. package/backend-nestjs/src/core/prisma/prisma.service.ts +17 -0
  42. package/backend-nestjs/src/core/prisma/schema.prisma +27 -0
  43. package/backend-nestjs/src/main.ts +26 -0
  44. package/backend-nestjs/src/module/auth/auth.controller.ts +30 -0
  45. package/backend-nestjs/src/module/auth/auth.module.ts +10 -0
  46. package/backend-nestjs/src/module/auth/auth.service.ts +83 -0
  47. package/backend-nestjs/src/module/auth/dto/index.ts +28 -0
  48. package/backend-nestjs/src/module/index.module.ts +17 -0
  49. package/backend-nestjs/src/scripts/migrate.js +40 -0
  50. package/backend-nestjs/tsconfig.build.json +4 -0
  51. package/backend-nestjs/tsconfig.json +22 -0
  52. package/frontend/.env.example +1 -0
  53. package/frontend/README.md +54 -0
  54. package/frontend/eslint.config.js +28 -0
  55. package/frontend/index.html +13 -0
  56. package/frontend/package-lock.json +3813 -0
  57. package/frontend/package.json +43 -0
  58. package/frontend/public/vite.svg +1 -0
  59. package/frontend/src/App.tsx +24 -0
  60. package/frontend/src/assets/react.svg +1 -0
  61. package/frontend/src/components/Layout.tsx +59 -0
  62. package/frontend/src/components/ui/AlertDialog.tsx +47 -0
  63. package/frontend/src/components/ui/Button.tsx +61 -0
  64. package/frontend/src/components/ui/CommonAlertDialog.tsx +57 -0
  65. package/frontend/src/components/ui/FormInput.tsx +73 -0
  66. package/frontend/src/components/ui/Loader.tsx +7 -0
  67. package/frontend/src/hook/useFetchUser.ts +38 -0
  68. package/frontend/src/index.css +1 -0
  69. package/frontend/src/lib/constants.ts +24 -0
  70. package/frontend/src/lib/schema.ts +12 -0
  71. package/frontend/src/lib/utils.ts +71 -0
  72. package/frontend/src/main.tsx +11 -0
  73. package/frontend/src/pages/Home.tsx +5 -0
  74. package/frontend/src/pages/Login.tsx +67 -0
  75. package/frontend/src/pages/Signup.tsx +67 -0
  76. package/frontend/src/redux/api/auth.ts +19 -0
  77. package/frontend/src/redux/slice/auth.ts +39 -0
  78. package/frontend/src/redux/store.ts +30 -0
  79. package/frontend/src/routes/index.tsx +20 -0
  80. package/frontend/src/routes/middleware.ts +18 -0
  81. package/frontend/src/types/index.ts +12 -0
  82. package/frontend/src/vite-env.d.ts +1 -0
  83. package/frontend/tsconfig.app.json +26 -0
  84. package/frontend/tsconfig.json +7 -0
  85. package/frontend/tsconfig.node.json +24 -0
  86. package/frontend/vite.config.ts +19 -0
  87. package/package.json +34 -0
  88. package/scripts/setup.js +73 -0
@@ -0,0 +1,67 @@
1
+ import { zodResolver } from "@hookform/resolvers/zod";
2
+ import { SignupFormData } from "../types";
3
+ import { useForm } from "react-hook-form";
4
+ import { SignupSchema } from "../lib/schema";
5
+ import { AUTH_TOKEN, SignUpFields } from "../lib/constants";
6
+ import { FormInput } from "../components/ui/FormInput";
7
+ import { Button } from "../components/ui/Button";
8
+ import { Link } from "react-router-dom";
9
+ import { handleError, handleSuccess } from "../lib/utils";
10
+ import { useRegisterMutation } from "../redux/api/auth";
11
+ import { actions } from "../redux/store";
12
+
13
+ export default function Signup() {
14
+ const [registerApi, { isLoading }] = useRegisterMutation();
15
+
16
+ const {
17
+ register,
18
+ handleSubmit,
19
+ formState: { errors }
20
+ } = useForm<SignupFormData>({
21
+ resolver: zodResolver(SignupSchema)
22
+ });
23
+
24
+ const onSubmit = async (data: SignupFormData) => {
25
+ const { data: res, error } = await registerApi(data);
26
+ if (error) {
27
+ handleError(error);
28
+ } else {
29
+ localStorage.setItem(AUTH_TOKEN, res.token);
30
+ actions.auth.setToken(res.token);
31
+ handleSuccess(res);
32
+ }
33
+ };
34
+ return (
35
+ <div className="h-full flex items-center justify-center bg-white">
36
+ <div className="w-full sm:max-w-[500px] p-4 sm:p-8 sm:py-10 sm:shadow-md rounded-lg space-y-8">
37
+ <div className="text-center text-3xl font-bold text-gray-900">
38
+ Signup
39
+ </div>
40
+
41
+ <form className="space-y-6" onSubmit={handleSubmit(onSubmit)}>
42
+ {SignUpFields.map(({ name, ...rest }) => (
43
+ <FormInput
44
+ key={name}
45
+ name={name}
46
+ register={register}
47
+ error={errors?.[name]?.message}
48
+ {...rest}
49
+ />
50
+ ))}
51
+
52
+ <Button
53
+ type="submit"
54
+ className="w-full text-md"
55
+ isLoading={isLoading}
56
+ >
57
+ Signup
58
+ </Button>
59
+
60
+ <Link to="/" className="text-[14px] text-blue-500 underline">
61
+ Already have an account? Sign in
62
+ </Link>
63
+ </form>
64
+ </div>
65
+ </div>
66
+ );
67
+ }
@@ -0,0 +1,19 @@
1
+ import { createApi } from "@reduxjs/toolkit/query/react";
2
+ import { baseQueryInterceptor } from "../../lib/utils";
3
+
4
+ export const authApi = createApi({
5
+ reducerPath: "authApi",
6
+ baseQuery: baseQueryInterceptor("/api/auth"),
7
+ endpoints: (builder) => ({
8
+ getUser: builder.query({ query: () => "/profile" }),
9
+ login: builder.mutation({
10
+ query: (body) => ({ url: "/login", method: "POST", body })
11
+ }),
12
+ register: builder.mutation({
13
+ query: (body) => ({ url: "/register", method: "POST", body })
14
+ })
15
+ })
16
+ });
17
+
18
+ export const { useLazyGetUserQuery, useLoginMutation, useRegisterMutation } =
19
+ authApi;
@@ -0,0 +1,39 @@
1
+ import { createSlice } from "@reduxjs/toolkit";
2
+ import { User } from "../../types";
3
+ import { AUTH_TOKEN } from "../../lib/constants";
4
+
5
+ interface AuthState {
6
+ currentUser: null | User;
7
+ loading: boolean;
8
+ token: string | null;
9
+ }
10
+
11
+ const initialState: AuthState = {
12
+ currentUser: null,
13
+ loading: false,
14
+ token: null
15
+ };
16
+
17
+ export const authSlice = createSlice({
18
+ name: "auth",
19
+ initialState,
20
+ reducers: {
21
+ setCurrentUser: (state, action) => {
22
+ state.currentUser = action.payload;
23
+ },
24
+ setIsLoading: (state, action) => {
25
+ state.loading = action.payload;
26
+ },
27
+ setToken: (state, action) => {
28
+ state.token = action.payload;
29
+ },
30
+ clearToken: (state) => {
31
+ localStorage.removeItem(AUTH_TOKEN);
32
+ state.currentUser = null;
33
+ state.token = null;
34
+ }
35
+ }
36
+ });
37
+
38
+ export const { setIsLoading, setCurrentUser } = authSlice.actions;
39
+ export default authSlice.reducer;
@@ -0,0 +1,30 @@
1
+ import { configureStore } from "@reduxjs/toolkit";
2
+ import authReducer, { authSlice } from "./slice/auth";
3
+ import { authApi } from "./api/auth";
4
+
5
+ export const store = configureStore({
6
+ reducer: {
7
+ auth: authReducer,
8
+ [authApi.reducerPath]: authApi.reducer
9
+ },
10
+ middleware: (getDefaultMiddleware) =>
11
+ getDefaultMiddleware().concat(authApi.middleware)
12
+ });
13
+
14
+ export type RootState = ReturnType<typeof store.getState>;
15
+ export type AppDispatch = typeof store.dispatch;
16
+
17
+ const createActions = (slice: any) => {
18
+ const actions: any = {};
19
+
20
+ Object.keys(slice.actions).forEach((key) => {
21
+ actions[key] = (payload: any) =>
22
+ store.dispatch(slice.actions[key](payload));
23
+ });
24
+
25
+ return actions;
26
+ };
27
+
28
+ export const actions = {
29
+ auth: createActions(authSlice)
30
+ };
@@ -0,0 +1,20 @@
1
+ import { lazy, Suspense } from "react";
2
+ import { Navigate, Route, Routes } from "react-router-dom";
3
+ import { Loader } from "../components/ui/Loader";
4
+
5
+ const Home = lazy(() => import("../pages/Home"));
6
+ const Login = lazy(() => import("../pages/Login"));
7
+ const Signup = lazy(() => import("../pages/Signup"));
8
+
9
+ export default function PageRoutes() {
10
+ return (
11
+ <Suspense fallback={<Loader />}>
12
+ <Routes>
13
+ <Route path="/" element={<Home />} />
14
+ <Route path="/login" element={<Login />} />
15
+ <Route path="/signup" element={<Signup />} />
16
+ <Route path="*" element={<Navigate to="/" replace />} />
17
+ </Routes>
18
+ </Suspense>
19
+ );
20
+ }
@@ -0,0 +1,18 @@
1
+ import { useEffect } from 'react'
2
+ import { useSelector } from 'react-redux'
3
+ import { useLocation, useNavigate } from 'react-router-dom'
4
+ import { RootState } from '../redux/store'
5
+
6
+ export const AuthHandler = () => {
7
+ const navigate = useNavigate()
8
+ const { pathname } = useLocation()
9
+ const { currentUser, loading } = useSelector((state: RootState) => state.auth)
10
+
11
+ useEffect(() => {
12
+ if (!loading) {
13
+ if (!currentUser && !['/login', '/signup'].includes(pathname)) navigate('/login')
14
+ else if (currentUser && ['/login', '/signup'].includes(pathname)) navigate('/')
15
+ }
16
+ }, [loading, currentUser, pathname])
17
+ return null
18
+ }
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import { SigninSchema, SignupSchema } from "../lib/schema";
3
+
4
+ export interface User {
5
+ _id: string;
6
+ name: string;
7
+ email: string;
8
+ }
9
+
10
+ export type LoginFormData = z.infer<typeof SigninSchema>;
11
+
12
+ export type SignupFormData = z.infer<typeof SignupSchema>;
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2020",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "isolatedModules": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+
18
+ /* Linting */
19
+ "strict": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["src"]
26
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "isolatedModules": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedSideEffectImports": true
22
+ },
23
+ "include": ["vite.config.ts"]
24
+ }
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react-swc';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+
5
+ const apiUrl = import.meta.env.VITE_API_URL;
6
+
7
+ // https://vite.dev/config/
8
+ export default defineConfig({
9
+ plugins: [react(), tailwindcss()],
10
+ server: {
11
+ proxy: {
12
+ '/api': {
13
+ target: apiUrl,
14
+ changeOrigin: true,
15
+ rewrite: (path) => path.replace(/^\/api/, '')
16
+ }
17
+ }
18
+ }
19
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@anmol0493/fullstack-app",
3
+ "version": "1.0.0",
4
+ "description": "A full-stack template with Vite React frontend and Express/NestJS backend options.",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "setup": "node scripts/setup.js"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Anmol0493/boilerplate.git"
13
+ },
14
+ "keywords": [
15
+ "fullstack",
16
+ "vite",
17
+ "react",
18
+ "express",
19
+ "nestjs"
20
+ ],
21
+ "author": "Anmol Dobariya",
22
+ "license": "MIT",
23
+ "bugs": {
24
+ "url": "https://github.com/Anmol0493/boilerplate/issues"
25
+ },
26
+ "homepage": "https://github.com/Anmol0493/boilerplate#readme",
27
+ "dependencies": {
28
+ "enquirer": "^2.4.1",
29
+ "execa": "^9.5.2"
30
+ },
31
+ "bin": {
32
+ "create-fullstack-app": "./scripts/setup.js"
33
+ }
34
+ }
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import enquirer from 'enquirer'; // Install enquirer: npm install enquirer
6
+ const { prompt } = enquirer;
7
+ import { execa } from 'execa'; // Install execa: npm install execa
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname } from 'path';
10
+
11
+ // Derive __dirname for ESM
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ // Get the project name from the command-line arguments
16
+ const projectName = process.argv[2];
17
+
18
+ if (!projectName) {
19
+ console.error('Please provide a project name:');
20
+ console.error(' npx @anmol040903/fullstack-app <project-name>');
21
+ process.exit(1);
22
+ }
23
+
24
+ // Define paths
25
+ const templateDir = path.join(__dirname, '..'); // Root directory of your template
26
+ const targetDir = path.resolve(process.cwd(), projectName);
27
+
28
+ // Ensure the target directory doesn't already exist
29
+ if (fs.existsSync(targetDir)) {
30
+ console.error(`Directory "${projectName}" already exists.`);
31
+ process.exit(1);
32
+ }
33
+
34
+ (async () => {
35
+ try {
36
+ // Step 1: Prompt the user to choose a backend
37
+ const response = await prompt({
38
+ type: 'select',
39
+ name: 'backend',
40
+ message: 'Which backend would you like to use?',
41
+ choices: ['Express', 'NestJS'],
42
+ });
43
+
44
+ const selectedBackend = response.backend.toLowerCase(); // e.g., "express" or "nestjs"
45
+ const backendSourceDir = path.join(templateDir, `backend-${selectedBackend}/`);
46
+ const backendTargetDir = path.join(targetDir, 'backend/');
47
+
48
+ // Step 2: Create the project directory and copy files
49
+ console.log(`Creating project "${projectName}"...`);
50
+ fs.mkdirSync(targetDir, { recursive: true }); // Create the project directory
51
+
52
+ // Copy frontend files
53
+ console.log('Copying frontend files...');
54
+ fs.cpSync(path.join(templateDir, 'frontend/'), path.join(targetDir, 'frontend/'), { recursive: true });
55
+
56
+ // Copy the selected backend files
57
+ console.log(`Setting up ${response.backend} backend...`);
58
+ fs.mkdirSync(backendTargetDir, { recursive: true });
59
+ fs.cpSync(backendSourceDir, backendTargetDir, { recursive: true });
60
+
61
+ // Step 3: Install dependencies for frontend and backend
62
+ console.log('Installing dependencies...');
63
+ process.chdir(targetDir); // Navigate to the new project directory
64
+
65
+ await execa('npm', ['install'], { cwd: path.join(targetDir, 'frontend'), stdio: 'inherit' });
66
+ await execa('npm', ['install'], { cwd: backendTargetDir, stdio: 'inherit' });
67
+
68
+ console.log(`Project ${projectName} created successfully!`);
69
+ } catch (error) {
70
+ console.error('An error occurred during setup:', error);
71
+ process.exit(1); // Exit with a failure code
72
+ }
73
+ })();