Skip to content

Starter.dev: Bootstrap your project with zero configuration!

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Starter.dev: Bootstrap your project with zero configuration!

Table of contents

We’re excited to present you with starter.dev, a set of zero-configuration project kits built with your favorite tools. Each kit is configured with the following so you can focus on building features instead of spending time on configuration:

  • State Management
  • Data Fetching
  • Testing
  • Storybook
  • Community standardized ESLint and Prettier

We’re also launching starter.dev showcases which utilizes the kits and demonstrates how to build at-scale applications and go beyond the basic TodoMVC app, though we love a good TodoMVC!

Why Starter Kits?

This Dot Labs engages with its clients to build modern applications, and many of them are requesting greenfield projects using the latest and greatest technologies.

While starting these projects, our architects found that they were repeating a bunch of the same steps each time they needed to start a new one. Most meta frameworks ship with the bare minimum, and don’t include features like testing or Storybook out of the box, and configuring these technologies can be time consuming.

With this challenge in mind, we sought to create zero-config starter templates to help kick start projects. And thus, starter.dev was born!

Why Showcases?

During starter.dev’s development, Minko Gechev from the Angular team approached us about a project to help enhance Angular education tooling. You can learn more about this specific effort in the blog post announcing the GitHub Clone Showcases.

Minko’s idea was to demonstrate building applications that utilize these key features of the Angular framework:

Routing Forms State Management API interactions - REST or GraphQL Authentication

This idea laid the groundwork for many of the starter kits we created. We’ve developed several GitHub Clone showcase to help developers understand how to best utilize the kits, and build at-scale applications that accompany our kits.

Getting Started

Getting started with starter.dev (pun intended) is as simple as running a scaffolding script:

  • Run npx @this-dot/create-starter to run the scaffolding tool
  • Select one of the kits from our library from the CLI
  • Name your project
  • cd into your project directory, install dependencies using the tool of your choice.

After completing these steps, you can start building features in your new project immediately.

What Kits Exist Today?

This Dot is happy to ship starter.dev with the following kits:

  • Angular + Apollo Client + Tailwind CSS
  • Angular + NgRx + SCSS
  • Create React App + RxJS + Styled Components
  • Next.js + TanStack Query (formerly React Query) + Tailwind CSS
  • Remix + GraphQL + Tailwind CSS
  • Vue 3 + Apollo Client + Quasar
  • Qwik + GraphQL + Tailwind CSS
  • SvelteKit + SASS

Each kit ships with the following out-of-the-box:

  • Testing via jest or vitest
  • Storybook
  • ESLint and Prettier Configuration
  • State Management
  • Data Fetching for either REST or GraphQL
  • Some starter components to demonstrate global state management and data fetching

These starter kits don’t ship with E2E testing libraries, such as Cypress or Playwright, for now, because these tools come with amazing out-of-the-box configurations and do not require additional setups. However, the showcases use Cypress tests consistency which you can check out in the showcases repo.

Collaborate with us

Starter.dev began as an internal need, but anyone can benefit from the existence of these kits. While there is a set structure for building out new kits, This Dot welcomes requests for new kits.

We’ll work with you to determine what the structure of your kit should be and then scaffold out all of the issues needed to complete the work. Our team will help build out new issues, but we encourage the community to jump in and help build as well. This is a great opportunity to collaborate with the community as both a learner and a teacher.

At This Dot, two of our guiding principles are: Getting Better Together and Giving Back to the Community. We believe that starter.dev is a perfect opportunity for us to get better through collaborative efforts with community members, and helping the community through what we hope is a great open source resource.

What’s Next?

We want to get this tool into your hands, and improve what exists. Tell us what is working for you and not, and we’ll do our best to address those issues.

Next, we want to expand our library of kits. Tell us what are some kits you would like to see. We’re looking into building out kits for SolidJS and Node backend technologies as part of our next iterations, but we’re sure there other tools people would like to see.

Finally, we’ll be publishing some additional educational materials around some of the decisions and design patterns we’ve chosen for each kit and showcase. We’re excited to share our process with you.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Unit Testing Qwik Components cover image

Unit Testing Qwik Components

Unit Testing Qwik Components Qwik is a new, superfast JavaScript framework from builder.io. Created by Miško Hevery, the author of AngularJS, Qwik aims to deliver instant loading web applications of any size or complexity through resumability. This is accomplished partially by delaying the execution and download of JavaScript for as long as possible while providing an excellent developer experience. > "You know React? You know Qwik." > — qwik.builder.io Given Miško's background as the creator of AngularJS, it's interesting that writing Qwik feels very similar to writing React. It even has a React compatibility mode. Personally, I have experience using mostly Angular, but I found that developing with Qwik was very smooth and enjoyable. So, although I am definitely hesitant to jump on new technologies, after attending Miško's workshop, browsing through all the resources at framework.dev tinkering with Qwik, and building a couple of apps with it, I became a fan. However, being a responsible software developer, I then wanted to add unit tests to my Qwik app. And because Qwik is relatively new, I found out there was little documentation on how to do this. Furthermore, until recently, there were no tools to easily set up and interact with Qwik components in unit tests. But that didn't stop me. I explored, asked, and waited for a PR to be merged, and now I am proud to present a comprehensive guide to unit testing Qwik apps. So, without further ado, let's dive into setting up our testing environment. Configuring a Testing Environment There are several options when it comes to choosing a testing framework, and I have even experimented with using Jest, as it is the popular choice for unit testing. However, since Qwik uses Vite, I found that using Vitest was the most straightforward and efficient option. Vitest is specifically designed to be the go-to test runner for Vite projects, which makes it a great fit. If you are familiar with Jest, you will be happy to know that the Vitest API is very similar, so you won't have to learn much to use it effectively. Before moving further to the actual setup, let's make sure you have a Qwik project to test. If you don't yet, you can simply create one by running npm create qwik@latest. Now, let's set up vitest in our Qwik project. It takes 3 easy steps: 1. Install Vitest as a dev dependency by running npm i vitest --save-dev from the root of your project. This will update the devDependencies array in your package.json file. 2. Update your vite.config.ts file to include a test configuration in the defineConfig function. You can start with an empty object for now, but you can always add configuration options later on. Your vite.config.ts should look something like this: ` 3. Update the "scripts" section of your package.json file to include commands for running vitest once, running it in watch mode, and for generating coverage reports. Your "scripts" section should look something like this: ` After completing these steps, you should be able to run npm run test and npm run coverage from your terminal to use Vitest. Note that if you run npm run test and you don't have any tests yet, which is likely the case at this point, you will see the message No test files found, exiting with code 1. Don't worry. We are about to add some tests shortly. Writing Tests By default, vitest will look for files with names that match the pattern '**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}. In a Qwik project that uses TypeScript and returns JSX elements, it makes sense to use the *.spec.tsx or *.test.tsx pattern. I prefer to use the.spec suffix. But .test works just as well, and is equally valid. Feel free to choose the one that you like best. To proceed with writing a unit test, we will need a component to test. For this example, we will use a simple counter component that has a div that displays a number and a button that increments it. To create the component, follow these steps: 1. Go to the src/components directory, and create a new folder called counter. 2. Inside the counter folder, create a new file called counter.tsx and add the following code: ` 3. Next to the counter.tsx file, create a new file called counter.spec.tsx file next to it. This file will contain the unit tests for the Counter component. Now that we have our component and our test file, we can proceed to write our unit tests. Let's open counter.spec.tsx and add some boilerplate along with a simple test to verify we have everything set up correctly: ` We have a describe method that groups related tests together (in our case, we're grouping all the tests we will write for our Counter component), an expect method which is used to make assertions about the values returned by the code being tested, and an it method that defines an individual test. If we now run npm run test, we should see the following output: ` Writing a Simple Test Case Now that we verified that our dummy test work, let's actually test our component's behavior. First, we want to test that it renders correctly, so we will remove our dummy test and leverage the new createDOM method from @builder.io/qwik.testing to render our Counter component inside the test: ` Rerunning the test should result in a pass if we did everything correctly. Now we know our component does render and displays zero as the count. Testing Interactions "Great," you say, "but I want to test the component's logic, not just the initial render!". Well, you're in luck because the createDOM method returns one more property - userEvent - that you can use to interact with the component's DOM. This allows us to simulate user interactions, such as clicking a button, and test whether the component responds correctly. For example, we can test whether our counter correctly increments the count by clicking the button. Here is the updated code example: ` With this additional test, we can be confident that our Counter component not only renders correctly, but also updates the count as expected when the increment button is clicked. Voilà, our component's behavior has been verified! Mocking Qwik Hooks So far, we have a test that verifies the component's logic, which is great! However, there may come a time when we need to mock parts of the component's logic to test certain scenarios. Unfortunately, here comes a little trade-off for the sake of Qwik's quickness. The component$ wrapper does not expose the internals of the component, so we cannot easily modify it for our tests. Fortunately, the most common thing we need to mock in Qwik components are hooks such as useLocation() or useStore(). We can do this by using vi.mock. As we can read in the Vitest docs, the vi.mock method takes two arguments: the path to the module that we want to mock, and a function that returns the mocked module. For example, if we want to modify the initial count in our useStore() hook to be _1_, we can mock the entire @builder.io/qwik module and return the actual module with the modified initial value. We can use the JavaScript bind method to do this. The call to vi.mock can be placed anywhere in the code as it is hoisted and will always be called before modules are imported, but for clarity, we will put it in a beforeAll block: ` Mocking the useLocation hook is even simpler, as it is not expected to change during a test. In this case, we can directly return an object from the mock. Here is an example of how to mock the useLocation hook: ` Conclusion As a newcomer to the world of JavaScript frameworks, Qwik offers a unique approach to building fast, efficient web applications. However, we shouldn't forget about the quality and robustness of our code. In this blog post, we provided a comprehensive guide on how to set up and write tests for your Qwik components. Setting up a testing environment for a Qwik app is relatively straightforward. Qwik uses Vite as its build tool, which means that the most efficient option for unit testing is to use Vitest, a test runner specifically designed for Vite projects. We showed how to install Vitest and update the Vite config file to include a test configuration, and went through examples of how to write tests for Qwik components, including how to mock hooks and test interactions with the DOM. By following the steps and examples in this post, you can easily add unit tests to your Qwik app, and ensure that your components are working correctly. This will give you confidence in your code, and allow you to build more complex and robust applications with Qwik. And if you found this article a bit overwhelming, do not worry, you can check out the Qwik starter kit which includes examples of already implemented tests. Have fun writing Qwik and reliable apps!...

How to Setup Storybook in a Qwik Project cover image

How to Setup Storybook in a Qwik Project

Introduction Storybook is a great tool for testing and visualizing your components in different states. In this article, we will see how to setup Storybook in a Qwik project. Qwik Qwik is a new JavaScript framework by Misko Hevery, creator of Angular, for building frontend browser applications. The major benefit of Qwik is its performance optimization, and this is achieved through zero loading, resumability, lazy loading, reduced rendering, scalability, and code once. For more information on Qwik, you can check out the docs, github repo, and discord. Project Set Up To get started, we need to create a new Qwik app. We can do this by running the following command in our terminal: ` Initialize Storybook Storybook, unfortunately, doesn’t have a Qwik template yet because it is a new Framework. So the work around is to use the html template . Using the command below, we can initialize Storybook: ` During the initialization, Storybook would ask to automatically install some optional dependencies such as; EslintPlugin and npm7, you can accept or reject it. To accept, type y and hit Enter to progress. Storybook would try to setup a default stories folder, which is based on the ` template we choose, which is `. It is not compatible with the Qwik compiler and will result in compilation errors, so we will have to delete it to avoid running into such errors. Project Structure Our project structure is already setup by Qwik, and Storybook initialization has created a .storybook folder. But we need to make some changes to the Storybook file extension since our project is in TypeScript. This is a snippet of the folders in our project: ` Configuring Storybook Since Qwik runs on Vite, we need to set up the viteFinal function in our main.ts file, which will give us the config that we will use to register our Qwik Vite plugin. Add this line of code in the configuration object: ` In the preview.ts file, this is where we configure how Storybook renders our stories. We need to execute Qwikloader. This will help in registering global browser events, and much more Qwik related benefits. We will replace the content in the file with the code below: ` This solution was found in this discussion: How to do component testing with Qwik?. I believe when the Qwik Storybook type template becomes available these configurations will be there by default. Now we are done with the configuration, let's run storybook and see what we have: ` Creating Stories We can create our first story, we will create a story for our Qwik app component. We will create this story for our default Qwik Header component. I modified the Header component to accept a menus props: ` Conclusion In this article, we saw how to setup Storybook in a Qwik project. We also saw how to create our first story. I hope you enjoyed this article. Thanks for reading. If you don't want to do these steps yourself, check out our starter.dev Qwik kit that already has Storybook enabled for your use here. A link to the project repo can be found here. If you have any questions or run into any trouble, feel free to reach out on Twitter....

Configure your project with Drizzle for Local & Deployed Databases cover image

Configure your project with Drizzle for Local & Deployed Databases

Updated April 8, 2024: Thanks to Jassi Bacha for pointing out that the migrate function is not exported from the drizzle-orm/postgres-js/migrator module for Vercel. I've updated the article to reflect the correct import path. It was a fun Friday, and Jason Lengstorf and I both decided to try and use Drizzle on our respective projects. Jason went the SQLite route and wrote an amazing article about how he got his setup working. My approach was a bit different. I started with Vercel's Postgres + Drizzle Next.js Starter and wanted to use PostgreSQL. If you don't know what Drizzle is, it's a type-safe ORM similar to Prisma. My colleague, Dane Grant, wrote a great intro post on it, so go check his article out if you want to learn more about Drizzle. Getting my project off the ground took longer than I expected, especially coming from a starter kit, but I figured it out. This is the article I wish I had at the time to help get this project set up with less friction. I will focus on using local and Vercel PostgreSQL, but this same setup should work with other databases and adapters. I'll make some notes about where those places are. While I did use Next.js here, these setup instructions work on other projects, too. Configuring Drizzle Every project that leverages Drizzle requires a drizzle.config in the root. Because I'm leveraging TypeScript, I named mine drizzle.config.ts, and to secure secrets, I also installed dotenv. My final file appeared as follows: ` The schema field is used to identify where your project's database schema is defined. Mine is in a file called schema.ts but you can split your schema into multiple files and use glob patterns to detect all of them. The out field determines where your migration outputs will be stored. I recommend putting them in a folder in the same directory as your schema to keep all your database-related information together. Additionally, the config requires a driver and dbCredentials.connectionString to be specified so Drizzle knows what APIs to leverage and where your database lives. For the connectionString, I'm using dotenv to store the value in a secret and protect it. The connectionString should be in a valid connection format for your database. For PostgreSQL, this format is postgresql://:@:/. Getting your connection string Now, you may be wondering how to get that connection string. If you're hosting on Vercel using their Postgres offering, you need to go to your Vercel dashboard and select their Postgres option and attach it to your app. This will set environment variables for you that you can pull into your local development environment. This is all covered in their "Getting Started with Vercel Postgres" guide. If you use a different database hosting solution, they'll offer similar instructions for fetching your connection string. However, I wanted a local database to modify and blow away as needed for this project. Out of the box, Drizzle does not offer a database initialization command and I needed something that could be easily and quickly replicated across different machines. For this, I pulled in Docker Compose and set up my docker-compose.yaml as follows: ` The 3 most important values to note here are the values in the environment key and the ports. These are what allowed me to determine my connection key. For this example, it would be: postgresql://postgres:postgres@localhost:5432/my-local-db. With the compose file set, I ran docker-compose up -d to get the container running, which also initializes the database. Now, we can connect and operate on the database as needed. Creating the database connection To make operations in our app, we need to get a database connection instance. I put mine in db/drizzle.ts to keep all my related database files together. My file looks like: ` This is a bit more complicated because we're using 2 different Drizzle adapters depending on our environment. For local development, we're using the generic PostgreSQL adapter, but for production, we're using the Vercel adapter. While these have different initializers, they have the same output interface which is why this works. The same wouldn't be true if you used MySQL locally and PostgreSQL in production. If we chose a RDS or similar PostgreSQL solution, we could use the same postgres adapter in both cases but change the connection string. That's all this file does at the end - detects which environment and uses the chosen adapter. If we go to use this exported instance, it won't be able to find our tables or provide type safety. This is because we haven't created our database tables yet. Creating database tables To get our database tables created, we're going to leverage Drizzle's Migrations. This allows us to create atomic changes to our database as our schema evolves. To accomplish this, we define the schema changes in our schema files as specified in our config. Then we can run npm run drizzle-kit generate:pg (or whatever script runner you use) to generate the migration SQL file that will be located where we specified in our config. You want to check this file into source! By default, Drizzle doesn't allow you to override migration names _yet_ (they're working on it!) so if you want to make your migration file more descriptive, you need to take both of these steps: 1. Rename the migration file. Take note of the old name. 2. Locate _journal.json. It should be in your migration folder in a folder called meta. From here, find the old file name and replace it with the new file name. Now, we need to run the migrations. I had some issues with top-level awaits and tsx like the Drizzle docs recommend, so I had to go a slightly different route and I'm not thrilled about it still. I made a file called migrate.mts that I stored next to my drizzle.ts. In theory, I should have been able to import my drizzle connection instance here and use that, but I ran out of time to figure it out and ended up repeating myself across files. Here's the file: ` Here, I'm connecting to the correct database pending environment then running the drizzle migrate command. For local development, I set my connection pool to max at 1. This probably isn't necessary for this use case, but when connecting to a cluster, this is a recommended best practice from the Drizzle team. For the local case, I also had to close the connection to the db when I was done. For both cases, though, I had to specify the migrations folder location. I could probably DRY this up a bit, but hopefully, the Drizzle team will eliminate this need and use the config to set this value in the future. With the above file set and our schema generated, we can now run npm run tsx db/migrate.mts and our database will have our latest schema. We can now use the db client to fetch and store data in our database. Note: Jason uses the push command here. This is fine for an initial database creation, but it will override tables in the future. The migration path is the recommended pattern for non-destructive database updates. Conclusion Congratulations! We can connect to our database and perform CRUD operations against our tables. We can also use Drizzle Studio to modify and inspect our data. To review, we had to: 1. Setup a local PostgreSQL server via a tool like Docker Compose 2. Configure the database adapter to work in local mode 3. Generate a schema 4. Create a script to execute migrations so our database is aligned with our schema This was my first experience with Drizzle, and I enjoyed its SQL-like interfaces which made it easy to quickly prototype my project. I noticed in their Discord that they're about to have a full-time maintainer so I'm excited to see what their future looks like. I hope you enjoy it too!...

Implementing Dynamic Types in Docusign Extension Apps cover image

Implementing Dynamic Types in Docusign Extension Apps

Implementing Dynamic Types in Docusign Extension Apps In our previous blog post about Docusign Extension Apps, Advanced Authentication and Onboarding Workflows with Docusign Extension Apps, we touched on how you can extend the OAuth 2 flow to build a more powerful onboarding flow for your Extension Apps. In this blog post, we will continue explaining more advanced patterns in developing Extension Apps. For that reason, we assume at least basic familiarity with how Extension Apps work and ideally some experience developing them. To give a brief recap, Docusign Extension Apps are a powerful way to embed custom logic into Docusign agreement workflows. These apps are lightweight services, typically cloud-hosted, that integrate at specific workflow extension points to perform custom actions, such as data validation, participant input collection, or interaction with third-party services. Each Extension App is configured using a manifest file. This manifest defines metadata such as the app's author, support links, and the list of extension points it uses (these are the locations in the workflow where your app's logic will be executed). The extension points that are relevant for us in the context of this blog post are GetTypeNames and GetTypeDefinitions. These are used by Docusign to retrieve the types supported by the Extension App and their definitions, and to show them in the Maestro UI. In most apps, these types are static and rarely change. However, they don't have to be. They can also be dynamic and change based on certain configurations in the target system that the Extension App is integrating with, or based on the user role assigned to the Maestro administrator on the target system. Static vs. Dynamic Types To explain the difference between static and dynamic types, we'll use the example from our previous blog post, where we integrated with an imaginary task management system called TaskVibe. In the example, our Extension App enabled agreement workflows to communicate with TaskVibe, allowing tasks to be read, created, and updated. Our first approach to implementing the GetTypeNames and GetTypeDefinitions endpoints for the TaskVibe Extension App might look like the following. The GetTypeNames endpoint returns a single record named task: ` Given the type name task, the GetTypeDefinitions endpoint would return the following definition for that type: ` As noted in the Docusign documentation, this endpoint must return a Concerto schema representing the type. For clarity, we've omitted most of the Concerto-specific properties. The above declaration states that we have a task type, and this type has properties that correspond to task fields in TaskVibe, such as record ID, title, description, assignee, and so on. The type definition and its properties, as described above, are static and they never change. A TaskVibe task will always have the same properties, and these are essentially set in stone. Now, imagine a scenario where TaskVibe supports custom properties that are also project-dependent. One project in TaskVibe might follow a typical agile workflow with sprints, and the project manager might want a "Sprint" field in every task within that project. Another project might use a Kanban workflow, where the project manager wants a status field with values like "Backlog," "ToDo," and so on. With static types, we would need to return every possible field from any project as part of the GetTypeDefinitions response, and this introduces new challenges. For example, we might be dealing with hundreds of custom field types, and showing them in the Maestro UI might be too overwhelming for the Maestro administrator. Or we might be returning fields that are simply not usable by the Maestro administrator because they relate to projects the administrator doesn't have access to in TaskVibe. With dynamic types, however, we can support this level of customization. Implementing Dynamic Types When Docusign sends a request to the GetTypeNames endpoint and the types are dynamic, the Extension App has a bit more work than before. As we've mentioned earlier, we can no longer return a generic task type. Instead, we need to look into each of the TaskVibe projects the user has access to, and return the tasks as they are represented under each project, with all the custom fields. (Determining access can usually be done by making a query to a user information endpoint on the target system using the same OAuth 2 token used for other calls.) Once we find the task definitions on TaskVibe, we then need to return them in the response of GetTypeNames, where each type corresponds to a task for the given project. This is a big difference from static types, where we would only return a single, generic task. For example: ` The key point here is that we are now returning one type per task in a TaskVibe project. You can think of this as having a separate class for each type of task, in object-oriented lingo. The type name can be any string you choose, but it needs to be unique in the list, and it needs to contain the minimum information necessary to be able to distinguish it from other task definitions in the list. In our case, we've decided to form the ID by concatenating the string "task_" with the ID of the project on TaskVibe. The implementation of the GetTypeDefinitions endpoint needs to: 1. Extract the project ID from the requested type name. 1. Using the project ID, retrieve the task definition from TaskVibe for that project. This definition specifies which fields are present on the project's tasks, including all custom fields. 1. Once the fields are retrieved, map them to the properties of the Concerto schema. The resulting JSON could look like this (again, many of the Concerto properties have been omitted for clarity): ` Now, type definitions are fully dynamic and project-dependent. Caching of Type Definitions on Docusign Docusign maintains a cache of type definitions after an initial connection. This means that changes made to your integration (particularly when using dynamic types) might not be immediately visible in the Maestro UI. To ensure users see the latest data, it's useful to inform them that they may need to refresh their Docusign connection in the App Center UI if new fields are added to their integrated system (like TaskVibe). As an example, a newly added custom field on a TaskVibe project wouldn't be reflected until this refresh occurs. Conclusion In this blog post, we've explored how to leverage dynamic types within Docusign Extension Apps to create more flexible integrations with external systems. While static types offer simplicity, they can be constraining when working with external systems that offer a high level of customization. We hope that this blog post provides you with some ideas on how you can tackle similar problems in your Extension Apps....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co