easy-starter 1.2.1 → 2.1.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.
@@ -4,23 +4,57 @@ This project was bootstrapped with `easy-starter`, giving you maximum freedom wi
4
4
 
5
5
  ## Features Included
6
6
 
7
- * **React 19 & TypeScript**: For a robust frontend experience.
7
+ * **React 19 & TypeScript 7.0**: A modern, state-of-the-art type-safe frontend experience.
8
8
  * **Express Backend**: A simple Node.js server.
9
- * **Parcel Bundler**: Lightning-fast builds with zero configuration.
10
- * **SSR Ready**: Powered by [`ssr-hook`](https://www.npmjs.com/package/ssr-hook).
11
- * **Type-Safe API**: Connected via [`typed-client-server-api`](https://www.npmjs.com/package/typed-client-server-api).
12
- * **Local Database**: Ready to use with [`easy-db-node`](https://www.npmjs.com/package/easy-db-node).
9
+ * **Vite v8 Bundler**: Zero-config client compilation with lightning-fast HMR and proxying.
10
+ * **Type-Safe API**: RPC communication via [`typed-client-server-api`](https://www.npmjs.com/package/typed-client-server-api).
11
+ * **Local Database**: local JSON-based document storage with [`easy-db-node`](https://www.npmjs.com/package/easy-db-node).
13
12
  * **Routing**: Managed by [`easy-page-router`](https://www.npmjs.com/package/easy-page-router).
13
+ <!-- --- AUTH START --- -->
14
+ * **User Authentication**: Pre-configured user registration, login dialogs, cookie rotation, and password reset flows via [`easy-user-auth`](https://www.npmjs.com/package/easy-user-auth).
15
+ <!-- --- AUTH END --- -->
16
+ <!-- --- SSR START --- -->
17
+ * **SSR Ready**: Server-side rendering and hydration powered by [`ssr-hook`](https://www.npmjs.com/package/ssr-hook).
18
+ <!-- --- SSR END --- -->
19
+ <!-- --- ANALYTICS START --- -->
20
+ * **Analytics**: Privacy-friendly local analytics tracked via [`easy-analytics`](https://www.npmjs.com/package/easy-analytics).
21
+ <!-- --- ANALYTICS END --- -->
22
+ <!-- --- OGIMAGE START --- -->
23
+ * **og:image Generation**: Dynamic page screenshotting for social previews using headless `puppeteer`.
24
+ <!-- --- OGIMAGE END --- -->
25
+ <!-- --- RESIZER START --- -->
26
+ * **Image Resizing**: Dynamic image resizing and WebP/JPEG conversion on the fly via [`easy-image-resizer`](https://www.npmjs.com/package/easy-image-resizer) with automatic disk caching.
27
+ <!-- --- RESIZER END --- -->
28
+
29
+ ---
14
30
 
15
31
  ## Getting Started
16
32
 
17
- To start developing, run:
33
+ To start developing:
18
34
 
19
- ```bash
20
- npm run dev
21
- ```
35
+ 1. Install dependencies (already completed by easy-starter):
36
+ ```bash
37
+ npm install
38
+ ```
39
+ <!-- --- AUTH START --- -->
40
+ 2. **Configure SMTP settings:**
41
+ Open the generated `.env` file and fill in your SMTP credentials. This is required for sending password recovery links to users:
42
+ ```env
43
+ MAIL_SMTP=smtp.example.com
44
+ MAIL_PORT=587
45
+ MAIL_USER=your-email@example.com
46
+ MAIL_PASSWORD=your-email-password
47
+ ```
48
+ <!-- --- AUTH END --- -->
49
+
50
+ 3. Run the development environment:
51
+ ```bash
52
+ npm run dev
53
+ ```
54
+
55
+ This concurrently starts your Express server (port `1111`) and the Vite bundler (port `5173`). Open [http://localhost:5173](http://localhost:5173) in your browser. The page will automatically reload when you make changes.
22
56
 
23
- This will concurrently start your Express server and the Parcel bundler. Open [http://localhost:1234](http://localhost:1234) in your browser. The page will automatically reload when you make changes.
57
+ ---
24
58
 
25
59
  ## Core Concepts & Examples
26
60
 
@@ -28,10 +62,10 @@ This will concurrently start your Express server and the Parcel bundler. Open [h
28
62
 
29
63
  ### 1. Defining your API
30
64
 
31
- Your API contract is defined in a single file (`types.d.ts`). This ensures your frontend and backend are always perfectly in sync.
65
+ Your API contract is defined in `types.ts`. This ensures your frontend and backend are always perfectly in sync.
32
66
 
33
67
  ```typescript
34
- // types.d.ts
68
+ // types.ts
35
69
  import { APIDefinition, Endpoint } from "typed-client-server-api";
36
70
 
37
71
  export type API = APIDefinition<{
@@ -45,21 +79,21 @@ On the server, you implement these endpoints:
45
79
 
46
80
  ```typescript
47
81
  // server/index.tsx
48
- import { initApi } from "typed-client-server-api";
82
+ import { setAPIBackend } from "typed-client-server-api";
49
83
  import { API } from "../types";
50
84
 
51
- initApi<API>(app, {
85
+ setAPIBackend<API>(app, {
52
86
  getItems: async () => {
53
- return db.select("item");
87
+ return selectArray("item");
54
88
  },
55
89
  addItem: async ({ name, value }) => {
56
- return db.insert("item", { name, value });
90
+ return insert("item", { name, value });
57
91
  }
58
92
  });
59
93
  ```
60
94
 
61
- ### 2. Fetching Data with SSR (`useSSRApi`)
62
-
95
+ ### 2. Fetching Data
96
+ <!-- --- SSR START --- -->
63
97
  To fetch data on the server during SSR and seamlessly hydrate it on the client without loading spinners, use `useSSRApi` in your React components.
64
98
 
65
99
  ```tsx
@@ -68,8 +102,6 @@ import React from "react";
68
102
  import { useSSRApi } from "../services/api";
69
103
 
70
104
  export default function Index() {
71
- // This will run on the server, fetch the data, embed it in the HTML,
72
- // and hydrate perfectly on the client. Fully type-safe!
73
105
  const [items, error, isLoading, reload] = useSSRApi.getItems({});
74
106
 
75
107
  if (isLoading) return <p>Loading...</p>;
@@ -82,25 +114,97 @@ export default function Index() {
82
114
  );
83
115
  }
84
116
  ```
117
+ <!-- --- SSR END --- -->
118
+ <!-- --- NO_SSR START --- -->
119
+ To fetch data on the client reactively, use `useApi` in your React components.
120
+
121
+ ```tsx
122
+ // src/pages/Index.tsx
123
+ import React from "react";
124
+ import { useApi } from "../services/api";
125
+
126
+ export default function Index() {
127
+ const [items, error, isLoading, reload] = useApi.getItems({});
128
+
129
+ if (isLoading) return <p>Loading...</p>;
130
+ if (error) return <p>Error: {error.message}</p>;
131
+
132
+ return (
133
+ <ul>
134
+ {items?.map(item => <li key={item._id}>{item.name}</li>)}
135
+ </ul>
136
+ );
137
+ }
138
+ ```
139
+ <!-- --- NO_SSR END --- -->
140
+
141
+ <!-- --- RESIZER START --- -->
142
+ ### 3. Dynamic Image Resizing (`easy-image-resizer`)
143
+
144
+ All images stored in `/public` (e.g., `/public/images/logo.png`) can be dynamically resized, cropped, and converted on the fly using `easy-image-resizer` Express middleware.
145
+
146
+ #### Using `<Img />` Component in React (TSX)
147
+
148
+ Use the `<Img />` component imported from `../components/Img` instead of standard `<img>` tags. It automatically appends query parameters and defaults to `"webp"` format for optimal performance:
149
+
150
+ ```tsx
151
+ import { Img } from "../components/Img";
152
+
153
+ export default function MyComponent() {
154
+ return (
155
+ <Img
156
+ src="/images/logo.png"
157
+ w={300}
158
+ h={150}
159
+ format="webp"
160
+ quality={85}
161
+ alt="App Logo"
162
+ />
163
+ );
164
+ }
165
+ ```
166
+
167
+ Available props on `<Img />`:
168
+ * `w` or `width`: Target width in pixels
169
+ * `h` or `height`: Target height in pixels
170
+ * `mw` or `maxWidth`, `mh` or `maxHeight`: Max dimension constraints
171
+ * `format` or `type`: `"webp"` (default), `"jpeg"`, or `"png"`
172
+ * `q` or `quality`: Image quality (1-100)
173
+
174
+ #### Using Image Resizing in CSS
175
+
176
+ You can also use URL query parameters directly inside CSS `url()` declarations:
177
+
178
+ ```css
179
+ .hero-banner {
180
+ background-image: url("/images/hero.png?w=1200&format=webp&q=85");
181
+ }
182
+ ```
183
+ <!-- --- RESIZER END --- -->
85
184
 
86
- ### 3. AI Integration (`.cursorrules`)
185
+ ### 4. AI Integration (`.cursorrules`)
87
186
 
88
- This template includes a pre-configured `.cursorrules` file in the root. If you develop using AI assistants such as Cursor, Windsurf, or GitHub Copilot, they will automatically read this file to understand the specific conventions of `easy-starter`:
187
+ This template includes a pre-configured `.cursorrules` file in the root. If you develop using AI assistants such as Cursor, Windsurf, or GitHub Copilot, they will automatically read this file to understand the specific conventions of `easy-starter`.
188
+
189
+ ---
89
190
 
90
191
  ## Scripts
91
192
 
92
- * `npm run dev` - Starts the development server.
193
+ * `npm run dev` - Starts backend and Vite dev server.
93
194
  * `npm run build` - Builds the application for production.
94
195
  * `npm start` - Starts the production server (run build first).
95
196
  * `npm run deploy` - (If included) Deploys your app to a remote server via SSH.
96
197
 
198
+ ---
199
+
97
200
  ## Project Structure
98
201
 
99
202
  * `/src` - Your React frontend code.
100
203
  * `/server` - Your Express backend code.
101
204
  * `/types.ts` - Shared TypeScript definitions for your API and Database.
102
205
  * `/.cursorrules` - Rules instructing AI coding assistants how to interact with the project stack.
103
- * `/public` - Static assets (images, fonts, etc.).
104
- * `/easy-db` - Where your local database JSON files are stored.
206
+ * `/public` - Static assets served at the root (images, favicon, site.webmanifest).
207
+ * `/easy-db-files` - Where uploaded database files are served.
208
+ * `/easy-db-backup` - Automated backup folder for your JSON collections.
105
209
 
106
210
  Enjoy building with freedom!
package/template/env CHANGED
@@ -1,6 +1,5 @@
1
1
  NODE_ENV=development # development/production
2
2
  SERVER_PORT=1111
3
- ADMIN_PASSWORD=admin
4
3
 
5
4
  SERVER_URL=https://your-production-url.com/
6
5
 
@@ -1,6 +1,7 @@
1
- .parcel-cache/
1
+ .vite/
2
2
  node_modules/
3
3
  dist/
4
+ cache/
4
5
  easy-db/
5
6
  easy-db-files/
6
7
  easy-db-backup/
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <link rel="apple-touch-icon" sizes="180x180" href="/images/icon.png?w=180&h=180&format=webp" />
7
+ <link rel="icon" type="image/png" sizes="32x32" href="/images/icon.png?w=32&h=32" />
8
+ <link rel="icon" type="image/png" sizes="16x16" href="/images/icon.png?w=16&h=16" />
9
+ <link rel="manifest" href="/site.webmanifest" />
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ <script type="module" src="/src/index.tsx"></script>
14
+ </body>
15
+ </html>
@@ -6,16 +6,19 @@
6
6
  "start": "tsx --env-file=.env server/index.tsx",
7
7
  "dev": "concurrently --kill-others \"npm run server:dev\" \"npm run fe:dev\"",
8
8
  "server:dev": "tsx --env-file=.env --watch server/index.tsx",
9
- "fe:dev": "parcel src/index.html --no-cache",
10
- "build": "parcel build src/index.html",
11
- "deploy": "tsx scripts/deploy.ts"
9
+ "fe:dev": "vite",
10
+ "build": "vite build"
12
11
  },
13
12
  "dependencies": {
14
13
  "cors": "^2.8.6",
15
14
  "easy-analytics": "^1.0.1",
16
15
  "easy-db-node": "^3.0.1",
16
+ "easy-image-resizer": "^2.0.1",
17
17
  "easy-page-router": "^0.2.1",
18
+ "easy-user-auth": "^1.1.0",
18
19
  "express": "^5.2.1",
20
+ "nodemailer": "^7.0.9",
21
+ "puppeteer": "^25.3.0",
19
22
  "react": "^19.2.6",
20
23
  "react-dom": "^19.2.6",
21
24
  "ssr-hook": "^0.2.0",
@@ -25,11 +28,14 @@
25
28
  "@types/cors": "^2.8.19",
26
29
  "@types/express": "^5.0.6",
27
30
  "@types/node": "^24.0.0",
28
- "@types/react": "^19.2.14",
29
- "@types/react-dom": "^19.2.3",
31
+ "@types/nodemailer": "^6.4.15",
32
+ "@types/react": "^19.0.0",
33
+ "@types/react-dom": "^19.0.0",
34
+ "@vitejs/plugin-react": "^6.0.3",
30
35
  "concurrently": "^9.2.1",
31
- "parcel": "^2.16.4",
36
+ "node-ssh": "^13.2.1",
32
37
  "tsx": "^4.21.0",
33
- "typescript": "^6.0.3"
38
+ "typescript": "^7.0.0",
39
+ "vite": "^8.1.5"
34
40
  }
35
- }
41
+ }
@@ -8,37 +8,31 @@
8
8
  "background_color": "#09090b",
9
9
  "icons": [
10
10
  {
11
- "src": "./images/icon.png?width=16&height=16",
11
+ "src": "/images/icon.png?w=16&h=16",
12
12
  "sizes": "16x16",
13
13
  "type": "image/png",
14
14
  "purpose": "maskable"
15
15
  },
16
16
  {
17
- "src": "./images/icon.png?width=32&height=32",
17
+ "src": "/images/icon.png?w=32&h=32",
18
18
  "sizes": "32x32",
19
19
  "type": "image/png",
20
20
  "purpose": "maskable"
21
21
  },
22
22
  {
23
- "src": "./images/icon.png?width=180&height=180",
23
+ "src": "/images/icon.png?w=180&h=180&format=webp",
24
24
  "sizes": "180x180",
25
- "type": "image/png",
26
- "purpose": "maskable"
27
- },
28
- {
29
- "src": "./images/icon.png?as=webp&width=192&height=192",
30
- "sizes": "192x192",
31
25
  "type": "image/webp",
32
26
  "purpose": "maskable"
33
27
  },
34
28
  {
35
- "src": "./images/icon.png?width=192&height=192",
29
+ "src": "/images/icon.png?w=192&h=192&format=webp",
36
30
  "sizes": "192x192",
37
- "type": "image/png",
31
+ "type": "image/webp",
38
32
  "purpose": "maskable"
39
33
  },
40
34
  {
41
- "src": "./images/icon.png?as=webp&width=512&height=512",
35
+ "src": "/images/icon.png?w=512&h=512&format=webp",
42
36
  "sizes": "512x512",
43
37
  "type": "image/webp",
44
38
  "purpose": "maskable"
@@ -17,7 +17,8 @@ const path = resolve(process.cwd()); // Local project path
17
17
  const ignoreFiles = [
18
18
  ".git",
19
19
  ".github",
20
- ".parcel-cache",
20
+ ".vite",
21
+ "dist",
21
22
  "node_modules",
22
23
  "scripts",
23
24
  "easy-db",
@@ -70,7 +71,7 @@ async function deploy() {
70
71
  const timeServerOff = new Date().getTime();
71
72
 
72
73
  console.log(`Removing old build...`);
73
- await sshExec(ssh, "rm -rf .parcel-cache", "Failed to remove .parcel-cache folder.");
74
+ await sshExec(ssh, "rm -rf .vite", "Failed to remove .vite folder.");
74
75
  await sshExec(ssh, "rm -rf dist", "Failed to remove dist folder.");
75
76
  console.log(`✔ Old build removed.\n`);
76
77