惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
J
Java Code Geeks
H
Help Net Security
B
Blog RSS Feed
G
Google Developers Blog
博客园 - 司徒正美
MongoDB | Blog
MongoDB | Blog
量子位
博客园 - 三生石上(FineUI控件)
The Cloudflare Blog
P
Proofpoint News Feed
小众软件
小众软件
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
V
V2EX
月光博客
月光博客
C
Check Point Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
A
Arctic Wolf
Help Net Security
Help Net Security
Schneier on Security
Schneier on Security
D
DataBreaches.Net
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Palo Alto Networks Blog
T
Tenable Blog
L
LangChain Blog
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
Forbes - Security
Forbes - Security
F
Fortinet All Blogs
Recent Announcements
Recent Announcements
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
大猫的无限游戏
大猫的无限游戏
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Y
Y Combinator Blog
WordPress大学
WordPress大学
Stack Overflow Blog
Stack Overflow Blog
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
NISL@THU
NISL@THU
GbyAI
GbyAI
博客园 - Franky
S
Secure Thoughts
有赞技术团队
有赞技术团队
PCI Perspectives
PCI Perspectives
U
Unit 42

Nx Blog

Sharing Tailwind CSS Styles Across Apps in a Monorepo | Nx Blog How SiriusXM Stays Competitive by Iterating and Getting to Market Fast | Nx Blog Agentic Experience Is the New Developer Experience | Nx Blog Nx Joins the Linux Foundation and the Agentic AI Foundation | Nx Blog A Monorepo Is NOT a Monolith | Nx Blog Why we deleted (most of) our MCP tools | Nx Blog Teach Your AI Agent How to Work in a Monorepo | Nx Blog How Broadcom stays efficient and nimble with monorepos | Nx Blog Why Monorepos are King in the Age of AI | Nx Blog Nx 2026 Roadmap: Expanding Agent Autonomy, Improving Performance, Better Polyglot and More | Nx Blog End to End Autonomous AI Agent Workflows with Nx | Nx Blog Autonomous Agents at Scale | Nx Blog Scaling 700+ Projects: How Nx Became a 'No-Brainer' for Caseware | Nx Blog Configure Tailwind v4 with Angular in an Nx Monorepo | Nx Blog The Missing Multiplier for AI Agent Productivity | Nx Blog A Year of Nx Webinars | Nx Blog Wrapping Up 2025 | Nx Blog Nx 22.3 Release: Angular 21 Support, tsgo Compiler, and Prettier v3 | Nx Blog Nx Cloud Release: Agent Resource Usage | Nx Blog Nx Platform Outperforms DIY Cache by 5x | Nx Blog An Nx Carol: Past, Present, and Future of Your Monorepo | Nx Blog Nx 22.1 Release: Terminal UI on Windows, Storybook 10, Vitest 4, and more! | Nx Blog The Compounding Effect: How Nx Features Multiply Performance Gains | Nx Blog 10 Monorepo Myths Debunked: Separating Fact from Fiction | Nx Blog Nx Cloud Release: Enterprise Task Analytics | Nx Blog Watch and Rebuild Storybook Dependencies with Nx | Nx Blog Book - React for Enterprise: Timeless Architecture for Enterprise Apps | Nx Blog Beyond Remote Cache: Unlock 70% More CI Performance | Nx Blog Nx 22 Release: Expanding the build platform | Nx Blog What's the Point of Generating All This Code If You Can't Merge It? | Nx Blog What's New in Nx Self-Healing CI | Nx Blog Nx Highlights: Smarter AI integration, all-new graph UI, and big new versions of your favorite tools | Nx Blog Making the Case for Smarter Monorepos, and How to Not Get Fooled by Myths | Nx Blog Integrating Biome in 20 Minutes | Nx Blog S1ngularity - What Happened, How We Responded, What We Learned | Nx Blog Stop Babysitting Your PRs: Self-Healing CI Cuts Time to Green by 50% | Nx Blog UKG Unifies Their Codebase and Eliminates CI Overhead to Focus on Customer Value | Nx Blog How Git Worktrees Changed My AI Agent Workflow | Nx Blog Nx Cloud Workspace Graph: See Your Organization's Code Structure Like Never Before | Nx Blog Seamless Java Deployment in Nx Using Docker | Nx Blog Getting Mobile Into Your Monorepo: Android + Nx | Nx Blog Polyglot Projects Made Easy: Integrating Spring Boot into an Nx Workspace | Nx Blog The Journey of the Nx Plugin for Gradle: From Prototype to Production | Nx Blog Combining Predictability and Intelligence With Nx Generators and AI | Nx Blog A New UI For The Humble Terminal | Nx Blog Continuous tasks are a huge DX improvement | Nx Blog New and Improved Module Federation Experience with Nx | Nx Blog A New UI for Nx Migration | Nx Blog Custom Task Runners and Self-Hosted Caching Changes | Nx Blog Enterprise Angular Monorepo Patterns | Nx Blog Using Rspack with Angular | Nx Blog Angular Architecture Guide To Building Maintainable Applications at Scale | Nx Blog Modern Angular Testing with Nx | Nx Blog Nx Update: 20.5 | Nx Blog Are Monorepos the Answer to Better AI-Assisted Development? | Nx Blog Making Cursor Smarter with an MCP Server For Nx Monorepos | Nx Blog React Development for 2025 | Nx Blog Using Apollo GraphQL in an Nx Workspace | Nx Blog Angular State Management for 2025 | Nx Blog Tailoring Nx for Your Organization | Nx Blog Nx Cloud Pipelines Come To Nx Console | Nx Blog Define the relationship with monorepos | Nx Blog See your affected project graph in Nx Cloud | Nx Blog Handling CORS In Your Workspace | Nx Blog Improve your architecture and CI pipeline times with Nx projects | Nx Blog Announcing Nx 20 | Nx Blog Introducing Nx Powerpack | Nx Blog Nx 19.5 is here! Stackblitz, Bun, Incremental Builds for Vite, Gradle Test Atomizer | Nx Blog Introducing Explain with AI | Nx Blog Nx Enterprise Podcast Episode 2: Tine Kondo | Nx Blog Monorepos and CI can be a Mess - Here's How Nx and Nx Cloud Fixed It | Nx Blog Nx Enterprise Podcast Episode 1: Hicham El Hammouchi | Nx Blog Nx 19.0 Release!! | Nx Blog Manage Your Gradle Project using Nx | Nx Blog Making the Argument for Monorepos | Nx Blog Reliable CI. A new execution model fixing both flakiness and slowness | Nx Blog Monorepos - Why Speed Matters | Nx Blog Nx Agents Walkthrough: Effortlessly Fast CI Built for Monorepos | Nx Blog Launch Nx Week Recap | Nx Blog Versioning and Releasing Packages in a Monorepo | Nx Blog Fast, Effortless CI | Nx Blog Introducing @nx/nuxt Enhanced Nuxt.js Support in Nx | Nx Blog What if Nx Plugins Were More Like VSCode Extensions | Nx Blog Monorepos: the Benefits, Challenges, and Importance of Tooling Support | Nx Blog Nx — Highlights of 2023 | Nx Blog Nx 17.2 Update | Nx Blog Unit Testing Expo Apps With Jest | Nx Blog Nx Docs AI Assistant | Nx Blog State Management Nx React Native/Expo Apps with TanStack Query and Redux | Nx Blog Nx 17 has Landed | Nx Blog Nx Conf 2023 — Recap | Nx Blog Nx Raises $16M Series A | Nx Blog Introducing Playwright Support for Nx | Nx Blog Nx 16.8 Release!!! | Nx Blog Step-by-Step Guide to Creating an Expo Monorepo with Nx | Nx Blog Create Your Own create-react-app CLI | Nx Blog Storybook Interaction Tests in Nx | Nx Blog Evergreen Tooling — More than Just CodeMods | Nx Blog Nx 16.5 Release!!! | Nx Blog A Practical Guide on Effective AI Use - AI as Your Peer Programmer | Nx Blog
Qwikify your Development with Nx | Nx Blog
Colum Ferry · 2023-08-16 · via Nx Blog

In the ever-evolving web development landscape, efficiency and modularity have become paramount. This is where Nx and Qwik come into play.

Qwik is a modern web framework that focuses on application performance by reducing the amount of JavaScript that needs to be shipped to the browser. You can learn more about how Qwik achieves this with Resumability in their docs.

Nx is a powerful tool that helps you build extensible and maintainable codebases that scale as your application and team grows. Nx utilises computation cache and workspace analysis to ensure maximum efficiency and developer experience. You can learn more about Nx here.

In this blog post, we'll explore how to combine the strengths of Nx and Qwik to create a todo app. To do this, we'll take advantage of an Nx Plugin that was created by the Qwikifiers team to maximise the integration between Qwik and Nx, called qwik-nx.

You do not necessarily need to use an Nx Plugin for Qwik. Instead, you could use the Qwik CLI to create your application and add Nx later. In this blog post we use the qwik-nx plugin to leverage better DX provided by the generators offered by the Plugin.

Table of Contents

  • Creating the Workspace
  • Generate the App
  • Generate a new Route
  • Build a Basic UI
  • Generate a Library
  • Add a Qwik Context
  • Using the Context
  • Adding a routeLoader$ to load data on Navigation
  • Handle the Form Action to add todos
  • Improve the Architecture
  • Conclusion
  • Further Reading
  • Learn more

You can learn more about this integration in the video below:

Creating the Workspace

Let's start by setting up our development environment. We'll create an Nx workspace and integrate Qwik into it. Begin by generating an empty integrated workspace:

npx create-nx-workspace@latest qwik-todo-app

You can also use the preset created by the qwik-nx plugin by running npx create-qwik-nx or npx -y create-nx-workspace@latest --preset=qwik-nx. This will skip a few of the next steps by installing the appropriate dependencies and generating your Qwik app.

The create-qwik-nx package is an example of creating an Install Package with Nx. You can learn more here: /extending-nx/recipes/create-install-package

Next, navigate into the workspace and install the qwik-nx plugin.

npm install --save-dev qwik-nx

You can view a compatibility matrix for which version of qwik-nx works with each version of nx here.

Generate the App

One of the benefits of using an Nx Plugin is that it comes with additional features such as automatic migrations, executors to act on your code and generators to scaffold code (like CodeMods).

Now, let's use the application generator provided by qwik-nx to scaffold the todo application:

This will generate the starter project that Qwik itself provides in your Nx Workspace. It will also install all the necessary packages to build a Qwik application.

At this point, you can already run the nx serve todo and nx build todo commands to have a feel around of the application that was created.

Generate a new Route

Qwik has another package called Qwik City that uses directory-based routing to handle navigation within your application. Learn more about directory-based routing with Qwik City.

The qwik-nx plugin can help generate new routes within our application. Let's use it to generate a route where we can store our todo logic.

nx g qwik-nx:route --name=todo --project=todo

After running this command, you'll see a new directory and file created in your workspace:

Create apps/todo/src/routes/todo/index.tsx

The newly created file should look like this:

apps/todo/src/routes/todo/index.tsx

import { component$ } from '@builder.io/qwik';

export default component$(() => {
  return <div>This is the todo</div>;
});

As you can see, it's very simple, just a standard Qwik Component.

If you run nx serve todo and navigate to http://localhost:4200/todo you can see that the route works and the component renders the content correctly.

Qwik displays the message "this is the todo"

Build a Basic UI

We want to build a todo application, so let's add some UI elements to make this look more like an actual todo application.

Update apps/todo/src/routes/todo/index.tsx to match the following:

apps/todo/src/routes/todo/index.tsx

import { component$ } from '@builder.io/qwik';
import { Form } from '@builder.io/qwik-city';

export default component$(() => {
  return (
    <div>
      <h1>Todos</h1>
      <div>
        <label>
          <input type="checkbox" /> {'My First Todo'}
        </label>
      </div>
      <Form>
        <input type="hidden" name="id" value={1} />
        <input type="text" name="message" />
        <button type="submit">Add</button>
      </Form>
    </div>
  );
});

You'll see the page update and look like the following:

Qwik now the heading, a labeled checkbox, and a formfield to add a todo

Awesome!

However, you'll notice that when you click Add, nothing happens! Let's add some logic to store new todos.

Generate a Library

Nx helps you organise your workspace in a modular fashion by creating workspace libraries that focus on specific functionality.

Instead of organising your features into subfolders of your application, with Nx, you'll extract them into workspace libraries (libraries that are not intended to be published, but still used by other libraries and applications in your repository). This helps to create a much stronger boundary between modules and features in your application as libraries have a public API (the index.ts file), allowing you to control exactly what can be accessed by consumers.

Learn more about defining and ensuring project boundaries in the Nx docs.

By doing this, you start to build out a project graph for your workspace and your application. Defining your architecture in this manner also helps to reduce the areas in your application that each change affects.

Learn more about the Project Graph.

Using this feature of Nx, we can organise the state management of our todo application into its own library, separating the logic from the application itself.

Let's generate a new library with the help of qwik-nx.

nx g qwik-nx:lib data-access

The list of files generated.

We do not need some of the files that were automatically generated so we can delete them:

libs/data-access/src/lib/data-access.tsx
libs/data-access/src/lib/data-access.css
libs/data-access/src/lib/data-access.spec.tsx

Add a Qwik Context

Qwik uses Contexts to help store state across both the server-side and client-side and across routes within the application.

We'll use a Context to store the todos in the application, but first, let's create a file to store the TS Interfaces we'll use in our application.

Create libs/data-access/src/lib/api.ts and add the following:

libs/data-access/src/lib/api.ts

export interface Todo {
  id: number;
  message: string;
}

Next, let's create a new file libs/data-access/src/lib/todo.context.tsx and add the following content:

libs/data-access/src/lib/todo.context.tsx

import {
  component$,
  createContextId,
  Slot,
  useContextProvider,
  useStore,
} from '@builder.io/qwik';
import { Todo } from './api';

interface TodoStore {
  todos: Todo[];
  lastId: number;
}

export const TodoContext = createContextId<TodoStore>('todo.context');
export const TodoContextProvider = component$(() => {
  const todoStore = useStore<TodoStore>({
    todos: [],
    lastId: 0,
  });
  useContextProvider(TodoContext, todoStore);
  return <Slot />;
});

This will create our Context and set up a Store within our application to store the todos. Qwik takes advantage of signals to update state and inform the framework of which components need to be re-rendered when the state changes.

Learn more about how Qwik uses Signals.

Finally, let's update the public entry point to the library to expose our Context and Interface.

Using the Context

Let's update the root page to add our Context Provider. Open apps/todo/src/root.tsx and add TodoContextProvider after QwikCityProvider in the component tree. Your file should look like the following:

import { component$, useStyles$ } from '@builder.io/qwik';
import {
  QwikCityProvider,
  RouterOutlet,
  ServiceWorkerRegister,
} from '@builder.io/qwik-city';
import { RouterHead } from './components/router-head/router-head';
import globalStyles from './global.css?inline';
import { TodoContextProvider } from '@qwik-todo-app/data-access';

export default component$(() => {
  /**
   * The root of a QwikCity site always start with the <QwikCityProvider> component,
   * immediately followed by the document's <head> and <body>.
   *
   * Don't remove the `<head>` and `<body>` elements.
   */
  useStyles$(globalStyles);
  return (
    <QwikCityProvider>
      <TodoContextProvider>
        <head>
          <meta charSet="utf-8" />
          <link rel="manifest" href="/manifest.json" />
          <RouterHead />
        </head>
        <body lang="en">
          <RouterOutlet />
          <ServiceWorkerRegister />
        </body>
      </TodoContextProvider>
    </QwikCityProvider>
  );
});

Update libs/data-access/src/index.ts to match the following:

libs/data-access/src/index.ts

export * from './lib/todo.context';
export * from './lib/api';

Now that our Context is in place, let's use it in our todo route to manage our todos.

Update apps/todo/src/routes/todo/index.tsx to match the following:

apps/todo/src/routes/todo/index.tsx

import { component$ } from '@builder.io/qwik';
import { Form } from '@builder.io/qwik-city';
import { TodoContext } from '@qwik-todo-app/data-access';

export default component$(() => {
  const todoStore = useContext(TodoContext);
  return (
    <div>
      <h1>Todos</h1>
      {todoStore.todos.map((t) => (
        <div key={`todo-${t.id}`}>
          <label>
            <input type="checkbox" /> {t.message}
          </label>
        </div>
      ))}
      <Form>
        <input type="hidden" name="id" value={1} />
        <input type="text" name="message" />
        <button type="submit">Add</button>
      </Form>
    </div>
  );
});

Our store has no todos in it when the application starts up, so if you serve the application you will no longer see any todos listed. Let's fix that!

Adding a routeLoader$ to load data on Navigation

Qwik allows you to fetch data when a route is navigated to, allowing you to fetch data before the page is rendered. The data will be fetched on the server before the component is rendered and downloaded to the client.

Learn more about routeLoader$.

It does this by providing a function called routeLoader$. We'll use this function to preload our store with some todos that will theoretically exist in a database.

For this blog post, we'll create an in-memory db to store some initial todos.

We'll start by updating our libs/data-access/src/lib/api.ts to add our in-memory DB.

libs/data-access/src/lib/api.ts

export interface Todo {
  id: number;
  message: string;
}

interface DB {
  store: Record<string, any[]>;
  get: (storeName: string) => any[];
  set: (storeName: string, value: any[]) => boolean;
  add: (storeName: string, value: any) => boolean;
}
export const db: DB = {
  store: { todos: [] },
  get(storeName) {
    return db.store[storeName];
  },
  set(storeName, value) {
    try {
      db.store[storeName] = value;
      return true;
    } catch (e) {
      return false;
    }
  },
  add(storeName, value) {
    try {
      db.store[storeName].push(value);
      return true;
    } catch (e) {
      return false;
    }
  },
};

Now that we have this, let's use it in our /todo route to load some data when the user navigates to /todo.

Update apps/todo/src/routes/todo/index.tsx to match the following:

apps/todo/src/routes/todo/index.tsx

import { component$ } from '@builder.io/qwik';
import { Form, routeLoader$ } from '@builder.io/qwik-city';
import { TodoContext, db } from '@qwik-todo-app/data-access';

export const useGetTodos = routeLoader$(() => {
  // A network request or db connection could be made here to fetch persisted todos
  // For illustrative purposes, we're going to seed a rudimentary in-memory DB if it hasn't been already
  // Then return the value from it
  if (db.get('todos')?.length === 0) {
    db.set('todos', [
      {
        id: 1,
        message: 'First todo',
      },
    ]);
  }
  const todos: Todo[] = db.get('todos');
  const lastId = [...todos].sort((a, b) => b.id - a.id)[0].id;
  return { todos, lastId };
});
export default component$(() => {
  const todoStore = useContext(TodoContext);
  const persistedTodos = useGetTodos();
  useTask$(({ track }) => {
    track(() => persistedTodos.value);
    if (persistedTodos.value) {
      todoStore.todos = persistedTodos.value.todos;
      todoStore.lastId =
        todoStore.lastId > persistedTodos.value.lastId
          ? todoStore.lastId
          : persistedTodos.value.lastId;
    }
  });
  return (
    <div>
      <h1>Todos</h1>
      {todoStore.todos.map((t) => (
        <div key={`todo-${t.id}`}>
          <label>
            <input type="checkbox" /> {t.message}
          </label>
        </div>
      ))}
      <Form>
        <input type="hidden" name="id" value={1} />
        <input type="text" name="message" />
        <button type="submit">Add</button>
      </Form>
    </div>
  );
});

When you serve the application, you'll see the first todo is fetched and rendered correctly!

Handle the Form Action to add todos

Qwik also allows you to handle form actions on the server using the routeAction$ API. Let's create the logic to add new todos to the store.

Learn more about routeAction$

Update apps/todo/src/routes/todo/index.tsx:

apps/todo/src/routes/todo/index.tsx

import { component$ } from '@builder.io/qwik';
import { Form, routeLoader$ } from '@builder.io/qwik-city';
import { TodoContext, db } from '@qwik-todo-app/data-access';

export const useGetTodos = routeLoader$(() => {
  // A network request or db connection could be made here to fetch persisted todos
  // For illustrative purposes, we're going to seed a rudimentary in-memory DB if it hasn't been already
  // Then return the value from it
  if (db.get('todos')?.length === 0) {
    db.set('todos', [
      {
        id: 1,
        message: 'First todo',
      },
    ]);
  }
  const todos: Todo[] = db.get('todos');
  const lastId = [...todos].sort((a, b) => b.id - a.id)[0].id;
  return { todos, lastId };
});
export const useAddTodo = routeAction$(
  (todo: { id: string; message: string }) => {
    const success = db.add('todos', {
      id: parseInt(todo.id),
      message: todo.message,
    });
    return { success };
  },
  zod$({ id: z.string(), message: z.string() })
);
export default component$(() => {
  const todoStore = useContext(TodoContext);
  const persistedTodos = useGetTodos();
  const addTodoAction = useAddTodo();

  useTask$(({ track }) => {
    track(() => persistedTodos.value);
    if (persistedTodos.value) {
      todoStore.todos = persistedTodos.value.todos;
      todoStore.lastId =
        todoStore.lastId > persistedTodos.value.lastId
          ? todoStore.lastId
          : persistedTodos.value.lastId;
    }
  });
  return (
    <div>
      <h1>Todos</h1>
      {todoStore.todos.map((t) => (
        <div key={`todo-${t.id}`}>
          <label>
            <input type="checkbox" /> {t.message}
          </label>
        </div>
      ))}
      <Form action={addTodoAction}>
        <input type="hidden" name="id" value={todoStore.lastId + 1} />
        <input type="text" name="message" />
        <button type="submit">Add</button>
      </Form>
      {addTodoAction.value?.success && <p>Todo added!</p>}
    </div>
  );
});

Awesome! We can now add todos to our application!

However, you might have noticed that our file is starting to get very long. Not only that there's a lot of logic in the route file itself. Let's use Nx to separate the logic into the library we created earlier to keep logic collocated.

Improve the Architecture

To separate the logic, create a new file libs/data-access/src/lib/todos.ts and move the logic for loading and adding todos into their own functions:

libs/data-access/src/lib/todos.ts

import { db, Todo } from './api';

export function getTodos() {
  // A network request or db connection could be made here to fetch persisted todos
  // For illustrative purposes, we're going to seed a rudimentary in-memory DB if it hasn't been already
  // Then return the value from it
  if (db.get('todos')?.length === 0) {
    db.set('todos', [
      {
        id: 1,
        message: 'First todo',
      },
    ]);
  }
  const todos: Todo[] = db.get('todos');
  const lastId = [...todos].sort((a, b) => b.id - a.id)[0].id;
  return { todos, lastId };
}
export function addTodo(todo: { id: string; message: string }) {
  const success = db.add('todos', {
    id: parseInt(todo.id),
    message: todo.message,
  });
  return { success };
}

Next, update libs/data-access/src/index.ts

libs/data-access/src/index.ts

export * from './lib/todo.context';
export * from './lib/api';
export * from './lib/todo';

Finally, let's update apps/todo/src/routes/todo/index.tsx to use our newly created functions:

apps/todo/src/routes/todo/index.tsx

import { component$, useContext, useTask$ } from '@builder.io/qwik';
import {
  Form,
  routeAction$,
  routeLoader$,
  z,
  zod$,
} from '@builder.io/qwik-city';
import { addTodo, getTodos, TodoContext } from '@acme/data-access';

export const useGetTodos = routeLoader$(() => getTodos());
export const useAddTodo = routeAction$(
  (todo) => addTodo(todo),
  zod$({ id: z.string(), message: z.string() })
);
export default component$(() => {
  const todoStore = useContext(TodoContext);
  const persistedTodos = useGetTodos();
  const addTodoAction = useAddTodo();
  useTask$(({ track }) => {
    track(() => persistedTodos.value);
    if (persistedTodos.value) {
      todoStore.todos = persistedTodos.value.todos;
      todoStore.lastId =
        todoStore.lastId > persistedTodos.value.lastId
          ? todoStore.lastId
          : persistedTodos.value.lastId;
    }
  });
  return (
    <div>
      <h1>Todos</h1>
      {todoStore.todos.map((t) => (
        <div key={`todo-${t.id}`}>
          <label>
            <input type="checkbox" /> {t.message}
          </label>
        </div>
      ))}
      <Form action={addTodoAction}>
        <input type="hidden" name="id" value={todoStore.lastId + 1} />
        <input type="text" name="message" />
        <button type="submit">Add</button>
      </Form>
      {addTodoAction.value?.success && <p>Todo added!</p>}
    </div>
  );
});

If you run nx serve todo again, you'll notice that our refactor will not have changed anything for the user, but it has made the codebase more manageable!

Now, if we need to update the logic for loading or adding todos, we only need to retest the library, and not the full application, improving our CI times!

Conclusion

The collaboration between Nx and Qwik has led us to create a todo app that showcases efficient development practices and modular design. By centralizing route logic in a library, we've not only demonstrated the capabilities of Nx and Qwik but also highlighted how this approach can significantly improve cache and CI times.

This journey through Qwik and Nx demonstrates how thoughtful architecture and the right tools can significantly enhance your development experience. So go ahead, Qwikify your development and build amazing web applications with ease!

Further Reading


Learn more