A quick reference for the habits we follow in this course. Keep this open while working on labs and projects.
Every commit message follows this format:
<type>(<scope>): <short description>
[optional body]
Types:
| Type | When to use |
|---|---|
feat |
Adding new functionality to a process or workflow |
fix |
Correcting a bug or wrong output |
refactor |
Restructuring code without changing behavior (e.g. migrating to static types) |
docs |
Updating comments, README, or this book |
test |
Adding or updating test profiles or data |
chore |
Config, CI, dependency updates |
perf |
Performance improvements |
Scope should be the process, workflow, or config file being changed.
Examples:
feat(quant): add salmon index caching
fix(fastqc): correct output record field name
refactor(rnaseq): migrate workflow to static types
docs(params): document samplesheet format
chore(config): add test profile defaults
Rule of thumb: commit after every logical unit of work — not at the end of the day, and not once per lab.
Parameters live in main.nf (typed, documented, defaulted). Execution configuration lives in nextflow.config.
main.nf — owns parametersparams {
// The input samplesheet of paired-end reads
reads: List<Sample> = "${projectDir}/data/samples.csv"
// The input transcriptome file
transcriptome: Path = "${projectDir}/data/ref.fa"
// Output directory
outdir: Path = "results"
// MultiQC config directory
multiqc: Path = "${projectDir}/multiqc"
}
nextflow.config — owns execution configprofiles {
test {
params.reads = "${projectDir}/data/test_samples.csv"
}
hpc {
process.executor = 'slurm'
process.memory = '16 GB'
}
}
NXF_SYNTAX_PARSERThis is a runtime environment variable — it cannot go in nextflow.config. Use a .env file in your project root instead:
# .env
NXF_SYNTAX_PARSER=v2
Commit this file so all collaborators use the same parser.
| Concern | Location |
|---|---|
| Parameter types, defaults, descriptions | params {} block in main.nf |
| Test data defaults | profiles { test {} } in nextflow.config |
| Resource and executor config | profiles { hpc {} } in nextflow.config |
| Parser version | .env |
Never use the legacy
params.foo = "..."syntax at the top ofmain.nf. The typedparams {}block replaces it entirely.
Enable static typing in every script that uses typed processes or workflows:
nextflow.preview.types = true
Tuple elements are accessed by index — records are accessed by name. Always prefer records.
// define a record type
record Sample {
id: String
fastq_1: Path
fastq_2: Path
}
// load samplesheet into a typed channel
read_pairs_ch = channel.fromList(params.reads)
process FASTQC {
input:
record(
id: String,
fastq_1: Path,
fastq_2: Path
)
output:
record(
id: id,
fastqc: file("fastqc_${id}_logs")
)
// ...
}
workflow RNASEQ {
take:
read_pairs_ch: Channel<Sample>
transcriptome: Path
// ...
emit:
samples: Channel<AlignedSample> = samples_ch
}
| Anti-pattern | Replacement |
|---|---|
tuple val(id), path(fastq_1) |
record(id: String, fastq_1: Path) |
Channel.from(...) |
channel.of(...) |
.splitCsv() as an operator |
.flatMap { csv -> csv.splitCsv() } |
channel.fromFilePairs(glob) |
channel.fromList(params.reads) with a samplesheet |
Implicit it closure parameter |
Declare the parameter explicitly: { v -> v * 2 } |
modules/
fastqc.nf // process FASTQC
quant.nf // process QUANT
index.nf // process INDEX
rnaseq.nf // workflow RNASEQ
main.nf // entry workflow only
Processes and subworkflows should be self-contained. Pass parameters in as arguments rather than accessing params globals inside a module.
// good — explicit
RNASEQ(read_pairs_ch, params.transcriptome)
// avoid — implicit global access inside a module
process QUANT {
script:
"salmon quant -i ${params.index} ..." // don't do this
}
| Anti-pattern | Replacement |
|---|---|
ch \| PROCESS \| map { ... } |
Standard assignments and method calls |
MY_WORKFLOW.out.foo |
Assign workflow outputs: out = MY_WORKFLOW(...) |
channel.of(1,2).set { ch } |
ch = channel.of(1, 2) |
channel.of(1,2).tap { ch } |
ch = channel.of(1, 2) |
each input qualifier in processes |
Use combine to produce a single tuple channel |
Globs (ggal_gut_{1,2}.fq) have no static type, fail silently on unexpected filenames, and can’t be validated before a run starts. Use a samplesheet instead.
id,fastq_1,fastq_2
gut,data/ggal/gut_1.fq,data/ggal/gut_2.fq
liver,data/ggal/liver_1.fq,data/ggal/liver_2.fq
lung,data/ggal/lung_1.fq,data/ggal/lung_2.fq
, as the separatorrecord field namesrecord Sample {
id: String
fastq_1: Path
fastq_2: Path
}
params {
reads: List<Sample> = "${projectDir}/data/samples.csv"
}
workflow {
read_pairs_ch = channel.fromList(params.reads)
// ...
}
Nextflow automatically parses the CSV and validates each row against the Sample record type. Invalid rows fail at startup, not mid-run.
| Approach | Type-safe | Validated at startup | Version-controllable |
|---|---|---|---|
| Glob pattern | No | No | No |
Samplesheet + List<Sample> |
Yes | Yes | Yes |