Skip to content

Commit fe7503b

Browse files
committed
Add LiquidJS view engine and static UI serving to server
Integrated LiquidJS as the view engine in the NestJS server and added a sample index.liquid view for debugging. Configured @nestjs/serve-static to serve the UI build from /assets/ui. Updated dependencies in server and UI packages, improved tsup config for UI, and adjusted React type versions.
1 parent 4d43861 commit fe7503b

8 files changed

Lines changed: 243 additions & 12 deletions

File tree

package-lock.json

Lines changed: 57 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/server/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
"@nestjs/common": "^10.0.0",
1414
"@nestjs/core": "^10.0.0",
1515
"@nestjs/platform-express": "^10.0.0",
16+
"@nestjs/serve-static": "^4.0.2",
1617
"@objectql/api": "*",
1718
"@objectql/core": "*",
1819
"@objectql/driver-knex": "*",
1920
"@objectql/driver-mongo": "*",
2021
"better-auth": "^1.4.10",
22+
"liquidjs": "^10.24.0",
2123
"pg": "^8.11.3",
2224
"reflect-metadata": "^0.1.13",
2325
"rxjs": "^7.8.1"
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { Controller, Get } from '@nestjs/common';
1+
import { Controller, Get, Render } from '@nestjs/common';
22
import { AppService } from './app.service';
33

44
@Controller()
55
export class AppController {
66
constructor(private readonly appService: AppService) {}
77

88
@Get()
9-
getHello(): string {
10-
return this.appService.getHello();
9+
@Render('index')
10+
root() {
11+
return { message: 'Hello world!' };
1112
}
1213
}

packages/server/src/app.module.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
import { Module } from '@nestjs/common';
1+
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
22
import { AppController } from './app.controller';
33
import { AppService } from './app.service';
44
import { ObjectQLModule } from './objectql/objectql.module';
55
import { AuthModule } from './auth/auth.module';
6+
import { ServeStaticModule } from '@nestjs/serve-static';
7+
import { join } from 'path';
68

79
@Module({
8-
imports: [ObjectQLModule, AuthModule],
10+
imports: [
11+
ObjectQLModule,
12+
AuthModule,
13+
ServeStaticModule.forRoot({
14+
rootPath: join(__dirname, '../../../ui/dist'),
15+
serveRoot: '/assets/ui',
16+
}),
17+
],
918
controllers: [AppController],
1019
providers: [AppService],
1120
})

packages/server/src/main.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { NestFactory } from '@nestjs/core';
22
import { AppModule } from './app.module';
3+
import { NestExpressApplication } from '@nestjs/platform-express';
4+
import { Liquid } from 'liquidjs';
5+
import { join } from 'path';
36

47
async function bootstrap() {
5-
const app = await NestFactory.create(AppModule);
8+
const app = await NestFactory.create<NestExpressApplication>(AppModule);
9+
10+
const engine = new Liquid();
11+
app.engine('liquid', engine.express());
12+
app.setViewEngine('liquid');
13+
app.setBaseViewsDir(join(__dirname, '..', 'views'));
14+
615
// NestJS by default listens on 3000
716
await app.listen(3000);
817
console.log(`Application is running on: ${await app.getUrl()}`);

packages/server/views/index.liquid

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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.0">
6+
<title>ObjectQL Server (Debug Mode)</title>
7+
<script src="https://cdn.tailwindcss.com"></script>
8+
9+
<!-- React Dependencies -->
10+
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
11+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
12+
13+
<!-- Babel for in-browser JSX compilation -->
14+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
15+
</head>
16+
<body class="bg-gray-100 min-h-screen">
17+
<div id="root" class="p-8"></div>
18+
19+
<script type="text/babel">
20+
const { useState, useEffect } = React;
21+
22+
// --- Inline Components for Debugging ---
23+
24+
function Button({ children, onClick, className }) {
25+
return (
26+
<button
27+
onClick={onClick}
28+
className={`px-4 py-2 bg-stone-900 text-white rounded hover:bg-stone-800 transition ${className}`}
29+
>
30+
{children}
31+
</button>
32+
);
33+
}
34+
35+
function FieldWrapper({ label, description, children }) {
36+
return (
37+
<div className="mb-4 space-y-1.5">
38+
<label className="block text-sm font-medium leading-none">{label}</label>
39+
{children}
40+
{description && <p className="text-[0.8rem] text-stone-500">{description}</p>}
41+
</div>
42+
);
43+
}
44+
45+
function StringField({ label, description, value, onChange }) {
46+
return (
47+
<FieldWrapper label={label} description={description}>
48+
<input
49+
type="text"
50+
value={value}
51+
onChange={(e) => onChange(e.target.value)}
52+
className="flex h-10 w-full rounded-md border border-stone-200 bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-stone-950"
53+
/>
54+
</FieldWrapper>
55+
);
56+
}
57+
58+
function NumberField({ label, description, value, onChange }) {
59+
return (
60+
<FieldWrapper label={label} description={description}>
61+
<input
62+
type="number"
63+
value={value}
64+
onChange={(e) => onChange(Number(e.target.value))}
65+
className="flex h-10 w-full rounded-md border border-stone-200 bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-stone-950"
66+
/>
67+
</FieldWrapper>
68+
);
69+
}
70+
71+
function BooleanField({ label, description, value, onChange }) {
72+
return (
73+
<div className="flex flex-row items-center justify-between rounded-lg border p-4">
74+
<div className="space-y-0.5">
75+
<label className="text-base font-medium">{label}</label>
76+
{description && <p className="text-sm text-stone-500">{description}</p>}
77+
</div>
78+
<input
79+
type="checkbox"
80+
checked={value}
81+
onChange={(e) => onChange(e.target.checked)}
82+
className="h-4 w-4"
83+
/>
84+
</div>
85+
);
86+
}
87+
88+
// --- Main App ---
89+
90+
function App() {
91+
const [name, setName] = useState('My Project');
92+
const [budget, setBudget] = useState(5000);
93+
const [isPublic, setIsPublic] = useState(true);
94+
95+
return (
96+
<div className="max-w-md mx-auto bg-white p-6 rounded-lg shadow-lg border space-y-6">
97+
<div>
98+
<h1 className="text-2xl font-bold">New Project</h1>
99+
<p className="text-stone-500">Create a new project using Inline Components</p>
100+
</div>
101+
102+
<div className="space-y-4">
103+
<StringField
104+
label="Project Name"
105+
description="Public display name"
106+
value={name}
107+
onChange={setName}
108+
/>
109+
110+
<NumberField
111+
label="Budget"
112+
description="Total budget in USD"
113+
value={budget}
114+
onChange={setBudget}
115+
/>
116+
117+
<BooleanField
118+
label="Public Access"
119+
description="Anyone can view this project"
120+
value={isPublic}
121+
onChange={setIsPublic}
122+
/>
123+
124+
<div className="pt-4 flex justify-end">
125+
<Button onClick={() => alert(JSON.stringify({ name, budget, isPublic }, null, 2))}>
126+
Create Project
127+
</Button>
128+
</div>
129+
</div>
130+
</div>
131+
);
132+
}
133+
134+
const root = ReactDOM.createRoot(document.getElementById('root'));
135+
root.render(<App />);
136+
</script>
137+
</body>
138+
</html>
139+

packages/ui/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"dist"
1010
],
1111
"scripts": {
12-
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
13-
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
12+
"build": "tsup",
13+
"dev": "tsup --watch",
1414
"lint": "eslint src/**",
1515
"test": "echo \"No tests specified\" && exit 0"
1616
},
@@ -19,8 +19,8 @@
1919
"react-dom": ">=18"
2020
},
2121
"devDependencies": {
22-
"@types/react": "^18.2.0",
23-
"@types/react-dom": "^18.2.0",
22+
"@types/react": "^18.3.27",
23+
"@types/react-dom": "^18.3.7",
2424
"autoprefixer": "^10.4.0",
2525
"postcss": "^8.4.0",
2626
"react": "^18.2.0",

packages/ui/tsup.config.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { defineConfig } from 'tsup'
2+
3+
export default defineConfig({
4+
entry: ['src/index.ts'],
5+
format: ['cjs', 'esm', 'iife'],
6+
globalName: 'ObjectQLUI',
7+
dts: true,
8+
clean: true,
9+
external: ['react', 'react-dom'],
10+
noExternal: ['clsx', 'tailwind-merge', 'class-variance-authority', '@radix-ui/react-label', '@radix-ui/react-slot', '@radix-ui/react-checkbox', 'lucide-react', 'react-hook-form', 'zod', '@hookform/resolvers'],
11+
define: {
12+
'process.env.NODE_ENV': '"production"'
13+
},
14+
minify: true,
15+
target: 'es2020'
16+
})

0 commit comments

Comments
 (0)