Add a New Qwik Project
The code for this example is available on GitHub:
Example repository/nrwl/nx-recipes/tree/main/qwik
Supported Features
We'll be using an Nx Plugin for Qwik called qwik-nx.
✅ Run Tasks ✅ Cache Task Results ✅ Share Your Cache ✅ Explore the Graph ✅ Distribute Task Execution ✅ Integrate with Editors ✅ Automate Updating Nx ✅ Enforce Module Boundaries ✅ Use Task Executors ✅ Use Code Generators ✅ Automate Updating Framework Dependencies
Install the qwik-nx Plugin
Install the qwik-nx
plugin:
❯
npm i --save-dev qwik-nx
You can find a compatibility matrix for qwik-nx
here: https://github.com/qwikifiers/qwik-nx#qwik-nx--nx-compatibility-chart.
You can use this to help you understand which version of qwik-nx
you should install based on the version of nx
you are currently using.
If you need help finding the version of nx
you are currently using, run nx report
.
Create the application
Let's generate a new application using qwik-nx
.
The command below uses the as-provided
directory flag behavior, which is the default in Nx 16.8.0. If you're on an earlier version of Nx or using the derived
option, omit the --directory
flag. See the workspace layout documentation for more details.
❯
nx g qwik-nx:app todo --directory=apps/todo
Create a library
Let's generate a new library using qwik-nx
.
The command below uses the as-provided
directory flag behavior, which is the default in Nx 16.8.0. If you're on an earlier version of Nx or using the derived
option, omit the --directory
flag. See the workspace layout documentation for more details.
❯
nx g qwik-nx:lib data-access --directory=libs/data-access
Create a Context in the library
We'll add a Context
to the library to store some state.
Create a new file libs/data-access/src/lib/todo.context.tsx
with the following content:
1import {
2 component$,
3 createContextId,
4 Slot,
5 useContextProvider,
6 useStore,
7} from '@builder.io/qwik';
8
9export interface Todo {
10 id: number;
11 message: string;
12}
13
14interface TodoStore {
15 todos: Todo[];
16 lastId: number;
17}
18
19export const TodoContext = createContextId<TodoStore>('todo.context');
20
21export const TodoContextProvider = component$(() => {
22 const todoStore = useStore<TodoStore>({
23 todos: [],
24 lastId: 0,
25 });
26
27 useContextProvider(TodoContext, todoStore);
28
29 return <Slot />;
30});
31
We'll use this context to store the state for our application.
Let's create a new file to handle some of the logic for our application.
Create libs/data-access/src/lib/todo.ts
and add the following:
1import { Todo } from './todo.context';
2
3// A rudimentary in-mem DB that will run on the server
4interface DB {
5 store: Record<string, any[]>;
6 get: (storeName: string) => any[];
7 set: (storeName: string, value: any[]) => boolean;
8 add: (storeName: string, value: any) => boolean;
9}
10
11export const db: DB = {
12 store: { todos: [] },
13 get(storeName) {
14 return db.store[storeName];
15 },
16 set(storeName, value) {
17 try {
18 db.store[storeName] = value;
19 return true;
20 } catch (e) {
21 return false;
22 }
23 },
24 add(storeName, value) {
25 try {
26 db.store[storeName].push(value);
27 return true;
28 } catch (e) {
29 return false;
30 }
31 },
32};
33
34export function getTodos() {
35 // A network request or db connection could be made here to fetch persisted todos
36 // For illustrative purposes, we're going to seed a rudimentary in-memory DB if it hasn't been already
37 // Then return the value from it
38 if (db.get('todos')?.length === 0) {
39 db.set('todos', [
40 {
41 id: 1,
42 message: 'First todo',
43 },
44 ]);
45 }
46 const todos: Todo[] = db.get('todos');
47 const lastId = [...todos].sort((a, b) => b.id - a.id)[0].id;
48 return { todos, lastId };
49}
50
51export function addTodo(todo: { id: string; message: string }) {
52 const success = db.add('todos', {
53 id: parseInt(todo.id),
54 message: todo.message,
55 });
56 return { success };
57}
58
Update libs/data-access/src/index.ts
to export our new context and methods:
1export * from './lib/todo.context';
2export * from './lib/todo';
3
Generate a Route
Next, let's generate a route to store the logic for the application.
❯
nx g qwik-nx:route --name=todo --project=todo
We will use our new context Update the new route file (apps/todo/src/routes/todo/index.tsx
) to the following:
1import { component$, useContext, useTask$ } from '@builder.io/qwik';
2import {
3 Form,
4 routeAction$,
5 routeLoader$,
6 z,
7 zod$,
8} from '@builder.io/qwik-city';
9import { addTodo, getTodos, TodoContext } from '@acme/data-access';
10
11export const useGetTodos = routeLoader$(() => getTodos());
12
13export const useAddTodo = routeAction$(
14 (todo) => addTodo(todo),
15 zod$({ id: z.string(), message: z.string() })
16);
17
18export default component$(() => {
19 const todoStore = useContext(TodoContext);
20 const persistedTodos = useGetTodos();
21 const addTodoAction = useAddTodo();
22
23 useTask$(({ track }) => {
24 track(() => persistedTodos.value);
25 if (persistedTodos.value) {
26 todoStore.todos = persistedTodos.value.todos;
27 todoStore.lastId =
28 todoStore.lastId > persistedTodos.value.lastId
29 ? todoStore.lastId
30 : persistedTodos.value.lastId;
31 }
32 });
33
34 return (
35 <div>
36 <h1>Todos</h1>
37 {todoStore.todos.map((t) => (
38 <div key={`todo-${t.id}`}>
39 <label>
40 <input type="checkbox" /> {t.message}
41 </label>
42 </div>
43 ))}
44 <Form action={addTodoAction}>
45 <input type="hidden" name="id" value={todoStore.lastId + 1} />
46 <input type="text" name="message" />
47 <button type="submit">Add</button>
48 </Form>
49 {addTodoAction.value?.success && <p>Todo added!</p>}
50 </div>
51 );
52});
53
Build and Serve the Application
To serve the application, run the following command and then navigate your browser to http://localhost:4200/todo
❯
nx serve todo
To build the application, run the following command:
❯
nx build todo