Show HN: I've been building an ERP for manufacturing for the last 3 years
The open-source operating system for manufacturing
Discord
·
Website
·
Issues
Does the world need another ERP?
We built Carbon after years of building end-to-end manufacturing systems with off-the-shelf solutions. We realized that:
- Modern, API-first tooling didn't exist
- Vendor lock-in bordered on extortion
- There is no "perfect ERP" because each company is unique
We built Carbon to solve these problems ☝️.
Architecture
Carbon is designed to make it easy for you to extend the platform by building your own apps through our API. We provide some examples to get you started in the examples folder.
Features:
Technical highlights:
Techstack
- Remix – framework
- Typescript – language
- Tailwind – styling
- Radix UI - behavior
- Supabase - database
- Supabase – auth
- Upstash - cache
- Trigger - jobs
- Resend – email
- Novu – notifications
- Vercel – hosting
- Stripe - billing
Codebase
The monorepo follows the Turborepo convention of grouping packages into one of two folders.
/apps
for applications/packages
for shared code
/apps
Package Name | Description | Local Command |
---|---|---|
erp |
ERP Application | npm run dev |
mes |
MES | npm run dev:mes |
academy |
Academy | npm run dev:academy |
starter |
Starter | npm run dev:starter |
/packages
Package Name | Description |
---|---|
eslint-config-carbon |
Shared, extendable eslint configuration for apps and packages |
@carbon/database |
Database schema, migrations and types |
@carbon/documents |
Transactional PDFs and email templates |
@carbon/integrations |
Integration definitions and configurations |
@carbon/jest |
Jest preset configuration shared across apps and packages |
@carbon/jobs |
Background jobs and workers |
@carbon/logger |
Shared logger used across apps |
@carbon/react |
Shared web-based UI components |
@carbon/kv |
Redis cache client |
@carbon/lib |
Third-party client libraries (slack, resend) |
@carbon/stripe |
Stripe integration |
@carbon/tsconfig |
Shared, extendable tsconfig configuration used across apps and packages |
@carbon/utils |
Shared utility functions used across apps and packages |
Development
Setup
-
Clone the repo into a public GitHub repository (or fork https://github.com/crbnos/carbon/fork). If you plan to distribute the code, keep the source code public to comply with AGPLv3. To clone in a private repository, acquire a commercial license
git clone https://github.com/crbnos/carbon.git
-
Go to the project folder
cd carbon
Make sure that you have Docker installed on your system since this monorepo uses the Docker for local development.
In addition you must configure the following external services:
Service | Purpose | URL |
---|---|---|
Upstash | Serverless Redis | https://console.upstash.com/login |
Trigger.dev | Job runner | https://cloud.trigger.dev/login |
Posthog | Product analytics platform | https://us.posthog.com/signup |
Each of these services has a free tier which should be plenty to support local development. If you're self hosting, and you don't want to use Upstash or Posthog, it's pretty easy to replace upstash with a redis container in @carbon/kv
and remove the Posthog analytics.
Installation
First download and initialize the repository dependencies.
$ nvm use # use node v20 $ npm install # install dependencies $ npm run db:start # pull and run the containers
Create an .env
file and copy the contents of .env.example
file into it
$ cp ./.env.example ./.env
- Use the output of
npm run db:start
to set the supabase entries:
SUPABASE_SERVICE_ROLE_KEY=[service_role key]
SUPABASE_ANON_KEY=[anon key]
- Create a Redis database in upstash and copy the following from the
REST API
section:
UPSTASH_REDIS_REST_URL=[UPSTASH_REDIS_REST_URL]
UPSTASH_REDIS_REST_TOKEN=[UPSTASH_REDIS_REST_TOKEN]
- Navigate to the project you created in https://cloud.trigger.dev/ and copy the following from the
Environments & API Keys
section:
TRIGGER_PUBLIC_API_KEY=[Public 'dev' API Key, starting 'pk_dev*']
TRIGGER_API_KEY=[Server 'dev' API Key, starting 'tr_dev*']
- In Posthog go to https://[region].posthog.com/project/[project-id]/settings/project-details to find your Project ID and Project API key:
POSTHOG_API_HOST=[https://[region].posthog.com]
POSTHOG_PROJECT_PUBLIC_KEY=[Project API Key starting 'phc*']
- Add a
STRIPE_SECRET_KEY
from the Stripe admin interface, and then runnpm run -w @carbon/stripe register:stripe
to get aSTRIP_WEBHOOK_SECRET
STRIPE_SECRET_KEY="sk_test_*************"
STRIP_WEBHOOK_SECRET="whsec_************"
Then you can run the following:
$ npm run db:build # run db migrations and seed script $ npm run build # build the packages
Finally, start the apps and packages:
$ npm run dev
$ npm run dev:mes # npm run dev in all apps & packages
You can now sign in with:
username: [email protected] password: carbon
After installation you should be able run the apps locally.
Application | URL |
---|---|
ERP | http://localhost:3000 |
MES | http://localhost:3001 |
Academy | http://localhost:4111 |
Starter | http://localhost:4000 |
Postgres | postgresql://postgres:postgres@localhost:54322/postgres |
Supabase Studio | http://localhost:54323/project/default |
Mailpit | http://localhost:54324 |
Edge Functions | http://localhost:54321/functions/v1/ |
Notes
To kill the database containers in a non-recoverable way, you can run:
$ npm run db:kill # stop and delete all database containers
To restart and reseed the database, you can run:
$ npm run db:build # runs db:kill, db:start, and setup
To run a particular application, use the -w workspace
flag.
For example, to run test command in the @carbon/react
package you can run:
$ npm run test -w @carbon/react
API
The API documentation is located in the ERP app at ${ERP}/x/api/js/intro
. It is auto-generated based on changes to the database.
There are two ways to use the API:
- From another codebase using a supabase client library:
- From within the codebase using our packages.
From another Codebase
Navigate to settings in the ERP to generate an API key. If you're self-hosting you can also use the supabase service key instead of the public key for root access. In that case you don't needto include the carbon-key
header.
import { Database } from "@carbon/database"; import { createClient } from "@supabase/supabase-js"; const apiKey = process.env.CARBON_API_KEY; const apiUrl = process.env.CARBON_API_URL; const publicKey = process.env.CARBON_PUBLIC_KEY; const carbon = createClient<Database>(apiUrl, publicKey, { global: { headers: { "carbon-key": apiKey, }, }, }); // returns items from the company associated with the api key const { data, error } = await carbon.from("item").select("*");
From the Monorepo
import { getCarbonServiceRole } from "@carbon/auth"; const carbon = getCarbonServiceRole(); // returns all items across companies const { data, error } = await carbon.from("item").select("*"); // returns items from a specific company const companyId = "xyz"; const { data, error } = await carbon .from("item") .select("*") .eq("companyId", companyId);
What's Your Reaction?






