Custom Module in Drupal 9

We will be creating a custom module in Drupal 9 to build a custom form & store the data in a custom content type.

Step 1: Name of the Module
We need to create a custom module under 'modules/custom' folder. We will name the folder as 'candidates'

Step 2: Creating the info.yml file
We have to create a yaml file with the machine name of our module for Drupal to be able recognize the module. We will name the file as 'candidates.info.yml'

name: Candidates Data
type: module
description: A custom module to enter candidate details
package: Custom
version: 1.0
core_version_requirement: ^8 || ^9

Step 3: Creating the routing.yml file
Next step is to add a candidates.routing.yml file under the 'candidates' directory.
Under path, we specify the URL path for adding Candidate data.
Under defaults, the _controller which references a method on the CandidatesController class and the _title specifies the default page title.
Under requirements, we specify the permission to view the page.

candidates.form:
 path: '/add-candidate'
 defaults:
  _title: 'Add Candidates'
  _form: '\Drupal\candidates\Form\CandidatesForm'
 requirements:
  _permission: 'access content'

Step 4: Adding the controller
We will create a controller file named 'CandidatesController.php' under 'modules/custom/candidates/src/Controller'. In this file, we will create a custom form to validate & submit candidate data. We will store the data into a custom content type named 'candidate'.

<?php

/**
 * @file
 * Contains \Drupal\candidates\Form\CandidatesForm.
*/

namespace Drupal\candidates\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\Entity\Node;

class CandidatesForm extends FormBase {
    /**
    * {@inheritdoc}
    */
    public function getFormId() {
        return 'candidates_form';
    }

    /**
    * {@inheritdoc}
    */
    public function buildForm(array $form, FormStateInterface $form_state) {
        $form['candidate_name'] = array(
            '#type' => 'textfield',
            '#title' => t('Candidate Name'),
            '#required' => TRUE,
        );
        $form['candidate_dob'] = array(
            '#type' => 'date',
            '#title' => t('Birth Date'),
            '#required' => TRUE,
        );
        $form['candidate_gender'] = array(
            '#type' => 'select',
            '#title' => ('Gender'),
            '#required' => TRUE,
            '#options' => array(
                'male' => t('Male'),
                'female' => t('Female'),
                'other' => t('Other'),
            ),
        );
        $form['candidate_mobile'] = array(
            '#type' => 'textfield',
            '#title' => t('Mobile'),
            '#required' => TRUE,
            '#maxlength' => 10,
        );
        $form['candidate_email'] = array(
            '#type' => 'email',
            '#title' => t('Email-ID'),
            '#required' => TRUE,
        );
        $form['candidate_city'] = array(
            '#type' => 'textfield',
            '#title' => t('City'),
            '#required' => TRUE,
        );
        $form['candidate_country'] = array(
            '#type' => 'textfield',
            '#title' => t('Country'),
            '#required' => TRUE,
        );
        $form['candidate_description'] = array(
            '#type' => 'textarea',
            '#title' => t('Description'),
            '#required' => TRUE,
        );
        $form['actions']['#type'] = 'actions';
        $form['actions']['submit'] = array(
            '#type' => 'submit',
            '#value' => $this->t('Save Candidate Data'),
            '#button_type' => 'primary',
        );
        return $form;
    }

    /**
    * {@inheritdoc}
    */
    public function validateForm(array &$form, FormStateInterface $form_state) {
        if ($form_state->getValue('candidate_name') == '') {
            $form_state->setErrorByName('candidate_name', $this->t('Please Enter Name'));
        }
        if ($form_state->getValue('candidate_dob') == '') {
            $form_state->setErrorByName('candidate_dob', $this->t('Please Enter Birth Date'));
        }
        if ($form_state->getValue('candidate_gender') == '') {
            $form_state->setErrorByName('candidate_gender', $this->t('Please Select Gender'));
        }
        if ($form_state->getValue('candidate_mobile') == '') {
            $form_state->setErrorByName('candidate_mobile', $this->t('Please Enter Mobile'));
        }
        if ($form_state->getValue('candidate_email') == '') {
            $form_state->setErrorByName('candidate_email', $this->t('Please Enter Email-ID'));
        }
        if ($form_state->getValue('candidate_city') == '') {
            $form_state->setErrorByName('candidate_city', $this->t('Please Enter City'));
        }
        if ($form_state->getValue('candidate_country') == '') {
            $form_state->setErrorByName('candidate_country', $this->t('Please Enter Country'));
        }
        if ($form_state->getValue('candidate_description') == '') {
            $form_state->setErrorByName('candidate_description', $this->t('Please Enter Description'));
        }
    }

    /**
    * {@inheritdoc}
    */
    public function submitForm(array &$form, FormStateInterface $form_state) {
        $node = Node::create(['type' => 'candidate']);
        $node->langcode = "en";
        $node->uid = 1;
        $node->promote = 0;
        $node->sticky = 0;
        $node->title= $form_state->getValue('candidate_name');
        $node->field_birth_date = $form_state->getValue('candidate_dob');
        $node->field_gender = $form_state->getValue('candidate_gender');
        $node->field_mobile = $form_state->getValue('candidate_mobile');
        $node->field_email_id = $form_state->getValue('candidate_email');
        $node->field_city = $form_state->getValue('candidate_city');
        $node->field_country = $form_state->getValue('candidate_country');
        $node->field_description = $form_state->getValue('candidate_description');
        $node->save();

        \Drupal::messenger()->addMessage(t("Candidate Data Save Successful"));
    }
}

Step 5: Creating custom content type
Create a custom content type named 'candidate' to store the data

23