Add a New Qwik Project

The code for this example is available on GitHub:

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 Code Generators ✅ Automate Updating Framework Dependencies

Install the qwik-nx Plugin

Install the qwik-nx plugin:

npm add -D qwik-nx

Nx Version Compatibility

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.

Directory Flag Behavior Changes

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.

Directory Flag Behavior Changes

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}); 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

More Documentation