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.
- package/README.md +33 -80
- package/bin/index.js +251 -49
- package/package.json +20 -11
- package/template/.cursorrules +42 -10
- package/template/README.md +129 -25
- package/template/env +0 -1
- package/template/gitignore +2 -1
- package/template/index.html +15 -0
- package/template/package.json +14 -8
- package/template/{src → public}/site.webmanifest +6 -12
- package/template/scripts/deploy.ts +3 -2
- package/template/server/index.tsx +264 -56
- package/template/server/mail.tsx +120 -0
- package/template/src/App.tsx +33 -7
- package/template/src/Router.tsx +13 -4
- package/template/src/components/Img.tsx +127 -0
- package/template/src/index.tsx +28 -1
- package/template/src/pages/Index.tsx +36 -6
- package/template/src/pages/NotFound.tsx +1 -1
- package/template/src/pages/ResetPassword.tsx +124 -0
- package/template/src/pages/admin/Admin.tsx +332 -85
- package/template/src/pages/admin/AnalyticsTab.tsx +71 -0
- package/template/src/pages/admin/ErrorsTab.tsx +46 -0
- package/template/src/pages/admin/UsersTab.tsx +54 -0
- package/template/src/services/api.ts +5 -7
- package/template/src/services/common.ts +34 -1
- package/template/src/styles.css +667 -0
- package/template/tsconfig.json +7 -2
- package/template/types.ts +44 -3
- package/template/vite.config.ts +29 -0
- package/template/.parcelrc +0 -6
- package/template/src/images/icon.png +0 -0
- package/template/src/index.html +0 -16
- package/template/src/styles.scss +0 -292
package/template/README.md
CHANGED
|
@@ -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**:
|
|
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
|
-
* **
|
|
10
|
-
* **
|
|
11
|
-
* **
|
|
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
|
|
33
|
+
To start developing:
|
|
18
34
|
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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 {
|
|
82
|
+
import { setAPIBackend } from "typed-client-server-api";
|
|
49
83
|
import { API } from "../types";
|
|
50
84
|
|
|
51
|
-
|
|
85
|
+
setAPIBackend<API>(app, {
|
|
52
86
|
getItems: async () => {
|
|
53
|
-
return
|
|
87
|
+
return selectArray("item");
|
|
54
88
|
},
|
|
55
89
|
addItem: async ({ name, value }) => {
|
|
56
|
-
return
|
|
90
|
+
return insert("item", { name, value });
|
|
57
91
|
}
|
|
58
92
|
});
|
|
59
93
|
```
|
|
60
94
|
|
|
61
|
-
### 2. Fetching Data
|
|
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
|
-
###
|
|
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
|
|
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,
|
|
104
|
-
* `/easy-db` - Where
|
|
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
package/template/gitignore
CHANGED
|
@@ -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>
|
package/template/package.json
CHANGED
|
@@ -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": "
|
|
10
|
-
"build": "
|
|
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/
|
|
29
|
-
"@types/react
|
|
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
|
-
"
|
|
36
|
+
"node-ssh": "^13.2.1",
|
|
32
37
|
"tsx": "^4.21.0",
|
|
33
|
-
"typescript": "^
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
29
|
+
"src": "/images/icon.png?w=192&h=192&format=webp",
|
|
36
30
|
"sizes": "192x192",
|
|
37
|
-
"type": "image/
|
|
31
|
+
"type": "image/webp",
|
|
38
32
|
"purpose": "maskable"
|
|
39
33
|
},
|
|
40
34
|
{
|
|
41
|
-
"src": "
|
|
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
|
-
".
|
|
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 .
|
|
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
|
|