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 as-provided vs. derived 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 as-provided vs. derived 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});
31We'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}
58Update libs/data-access/src/index.ts to export our new context and methods:
1export * from './lib/todo.context';
2export * from './lib/todo';
3Generate 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});
53Build 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