Project Setup
Before any application code, we need somewhere for it to live. This project is a Cargo workspace with two crates: pokemon_service, a library crate for application and domain logic, and pokemon_api, a binary crate responsible for HTTP and application startup.
$ mkdir pokemon_api_project && cd pokemon_api_project$ echo -e "[workspace]\nresolver = \"3\"" > ./Cargo.toml$ cargo new crates/pokemon-service --lib$ cargo new crates/pokemon-api --binThe directories and package names use hyphens, matching how crates.io names crates, while Rust code refers to them with underscores (use pokemon_service::...); Cargo translates between the two automatically, so both conventions are correct at once.
Cargo automatically discovers workspace members under the workspace root, so the Cargo.toml above is enough to build. For a tutorial though, it’s worth being explicit about which crates belong to the workspace rather than relying on discovery:
[workspace]resolver = "3"members = [ "crates/pokemon-service", "crates/pokemon-api",]Listing members explicitly isn’t mandatory, Cargo would find these two crates either way, but it communicates the intended workspace structure immediately to anyone opening the file for the first time.
.gitignore
target
# These are backup files generated by rustfmt**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information*.pdb
# Generated by cargo mutants# Contains mutation testing data**/mutants.out*/
# rustc will dump stack traces when hitting an internal compiler error to PWDrustc-ice-*.txt
# RustRover.idea/
# VSCode.vscode/
# Local environment values; never committed.env.env.local.env.*.localtarget/ is the directory Cargo actually creates for build output, so that’s the entry that matters, and the .gitignore rules out editor directories outright rather than leaving them commented out: nobody reading this repo later should have to guess whether an .idea/ or .vscode/ folder showing up in git status belongs there.
The environment entries are here a step early: we haven’t written any config yet, but once Part 2 introduces .env.local for real values, we want it gitignored from the start. A checked-in .env.example, the template with no real secrets in it, shows up alongside it then.
Initialize the git repo
$ git init$ git add .$ git commit -m "feat: Creating New Project <Pokemon API>"rustfmt config
A shared rustfmt.toml keeps formatting consistent across both crates from the start. edition and style_edition look redundant but control different things: edition sets the Rust language edition the code targets, while style_edition sets which version of rustfmt’s formatting rules to apply. The two often move together, but they don’t have to.
edition = "2024"style_edition = "2024"
max_width = 100
newline_style = "Unix"
use_small_heuristics = "Default"
use_try_shorthand = trueuse_field_init_shorthand = true
reorder_modules = true
group_imports = "StdExternalCrate"imports_granularity = "Crate"
wrap_comments = trueformat_code_in_doc_comments = trueMakefile
A Makefile gives us a small, consistent set of project-level commands that work the same locally and in CI, regardless of which crate we’re working on.
.PHONY: build check test fmt fmt-check clippy lint run clean
build: cargo build --workspace
check: cargo check --workspace
test: cargo test --workspace
fmt: cargo fmt --all
fmt-check: cargo fmt --all -- --check
clippy: cargo clippy --workspace --all-targets --all-features -- -D warnings
lint: fmt-check clippy
run: cargo run -p pokemon-api
clean: cargo cleanVerify the setup
Before moving on, it’s worth confirming all of this actually works:
$ make lint# output omitted$ make test# output omitted$ make check# output omitted$ make run# output omittedmake run won’t do anything interesting yet, main.rs is still whatever cargo new generated, but it should compile and start. At this point we have a valid Rust workspace with two independently compilable crates, shared formatting rules, and project-level development commands. Nothing useful happens yet, and that’s intentional. The next part starts on configuration.