diff --git a/src/main.cc b/src/main.cc
index fcbce97..f3416db 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -39,7 +39,18 @@ enum exit_codes : int {
   EXIT_CODE_GLAD_LOADER_FAILED,
 };
 
-std::string load_shader(const std::string &filepath);
+class Shader {
+  public:
+    Shader(const std::string &vert_file, const std::string &frag_file);
+    ~Shader();
+    void activate();
+  private:
+    GLuint program;
+    void link_program(GLuint vert, GLuint frag);
+    GLuint load_and_compile_shader(const std::string &filepath, GLenum shader_type);
+    std::string load_shader_from_file(const std::string &filepath);
+    static const char *get_shader_type_string(GLenum shader_type);
+};
 
 int main() {
   if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
@@ -105,52 +116,7 @@ int main() {
   glDisableVertexAttribArray(0);
   glDisableVertexAttribArray(1);
 
-  std::string vs_shader = load_shader("shaders/vert.glsl");
-  const char *vs_source = vs_shader.c_str();
-  GLuint vs = glCreateShader(GL_VERTEX_SHADER);
-  glShaderSource(vs, 1, &vs_source, NULL);
-  glCompileShader(vs);
-  GLint vs_compiled;
-  glGetShaderiv(vs, GL_COMPILE_STATUS, &vs_compiled);
-  if (vs_compiled != GL_TRUE)
-  {
-      GLsizei log_length = 0;
-      GLchar message[1024];
-      glGetShaderInfoLog(vs, 1024, &log_length, message);
-      printf("Failed to compile vertex shader: %s\n", message);
-  }
-
-  std::string fs_shader = load_shader("shaders/frag.glsl");
-  const char *fs_source = fs_shader.c_str();
-  GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
-  glShaderSource(fs, 1, &fs_source, NULL);
-  glCompileShader(fs);
-  GLint fs_compiled;
-  glGetShaderiv(fs, GL_COMPILE_STATUS, &fs_compiled);
-  if (fs_compiled != GL_TRUE)
-  {
-      GLsizei log_length = 0;
-      GLchar message[1024];
-      glGetShaderInfoLog(fs, 1024, &log_length, message);
-      printf("Failed to compile fragment shader: %s\n", message);
-  }
-
-  GLuint program = glCreateProgram();
-  glAttachShader(program, vs);
-  glAttachShader(program, fs);
-  glLinkProgram(program);
-  GLint program_linked;
-  glGetProgramiv(program, GL_LINK_STATUS, &program_linked);
-  if (program_linked != GL_TRUE)
-  {
-      GLsizei log_length = 0;
-      GLchar message[1024];
-      glGetProgramInfoLog(program, 1024, &log_length, message);
-      printf("Failed to link program: %s\n", message);
-  }
-
-  glDeleteShader(vs);
-  glDeleteShader(fs);
+  Shader main_shader {"shaders/vert.glsl", "shaders/frag.glsl"};
 
   bool running = true;
   SDL_Event event = {};
@@ -182,7 +148,7 @@ int main() {
     glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
     glClear(GL_COLOR_BUFFER_BIT);
 
-    glUseProgram(program);
+    main_shader.activate();
     glBindVertexArray(vao);
     glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, NULL);
 
@@ -196,7 +162,68 @@ int main() {
   return EXIT_CODE_SUCCESS;
 }
 
-std::string load_shader(const std::string &filepath) {
+
+Shader::Shader(const std::string &vert_file, const std::string &frag_file) {
+  GLuint vert = load_and_compile_shader(vert_file, GL_VERTEX_SHADER);
+  GLuint frag = load_and_compile_shader(frag_file, GL_FRAGMENT_SHADER);
+  link_program(vert, frag);
+  glDeleteShader(vert);
+  glDeleteShader(frag);
+}
+
+Shader::~Shader() {
+  if (program > 0) {
+    glDeleteProgram(program);
+  }
+}
+
+void Shader::activate() {
+  if (program > 0) {
+    glUseProgram(program);
+  }
+}
+
+void Shader::link_program(GLuint vert, GLuint frag) {
+  program = glCreateProgram();
+  glAttachShader(program, vert);
+  glAttachShader(program, frag);
+
+  glLinkProgram(program);
+  GLint program_linked;
+  glGetProgramiv(program, GL_LINK_STATUS, &program_linked);
+  if (program_linked != GL_TRUE)
+  {
+      GLsizei log_length = 0;
+      GLchar message[1024];
+      glGetProgramInfoLog(program, 1024, &log_length, message);
+      printf("Failed to link program: %s\n", message);
+      program = 0;
+  }
+}
+
+GLuint Shader::load_and_compile_shader(const std::string &filepath, GLenum shader_type) {
+  std::string src = load_shader_from_file(filepath);
+  const char *shader_src = src.c_str();
+
+  GLuint shader = glCreateShader(shader_type);
+  glShaderSource(shader, 1, &shader_src, NULL);
+  glCompileShader(shader);
+
+  GLint shader_compiled;
+  glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_compiled);
+  if (shader_compiled != GL_TRUE)
+  {
+      GLsizei log_length = 0;
+      GLchar message[1024];
+      glGetShaderInfoLog(shader, 1024, &log_length, message);
+      printf("Failed to compile %s shader: %s\n", get_shader_type_string(shader_type), message);
+      return 0;
+  }
+
+  return shader;
+}
+
+std::string Shader::load_shader_from_file(const std::string &filepath) {
   FILE *fp = fopen(filepath.c_str(), "r");
   if (!fp) {
     return "";
@@ -211,3 +238,33 @@ std::string load_shader(const std::string &filepath) {
 
   return output;
 }
+
+const char *Shader::get_shader_type_string(GLenum shader_type) {
+  const char *output;
+
+  switch (shader_type) {
+    case GL_COMPUTE_SHADER:
+      output = "compute";
+      break;
+    case GL_VERTEX_SHADER:
+      output = "vertex";
+      break;
+    case GL_TESS_CONTROL_SHADER:
+      output = "tess_control";
+      break;
+    case GL_TESS_EVALUATION_SHADER:
+      output = "tess_evaluation";
+      break;
+    case GL_GEOMETRY_SHADER:
+      output = "geometry";
+      break;
+    case GL_FRAGMENT_SHADER:
+      output = "fragment";
+      break;
+    default:
+      output = "UNKNOWN";
+      break;
+  }
+
+  return output;
+}